diff --git a/src/components/conversations/conversation-detail-panel.tsx b/src/components/conversations/conversation-detail-panel.tsx index 97aa744..1e86015 100644 --- a/src/components/conversations/conversation-detail-panel.tsx +++ b/src/components/conversations/conversation-detail-panel.tsx @@ -277,6 +277,11 @@ const ConversationTabView = memo(function ConversationTabView({ async (refreshConversationId: number) => { try { const refreshed = await refreshDetailCache(refreshConversationId) + // Skip ACK during prompting to avoid clearing liveMessage / + // resetting syncState while streaming. The useEffect with the + // connStatus === "prompting" guard will handle it naturally + // once prompting ends. + if (prevStatusRef.current === "prompting") return acknowledgePersistedDetail(refreshConversationId, refreshed) } catch (error) { setSyncState(refreshConversationId, "failed") diff --git a/src/components/message/message-list-view.tsx b/src/components/message/message-list-view.tsx index 824c6b6..b9cbf70 100644 --- a/src/components/message/message-list-view.tsx +++ b/src/components/message/message-list-view.tsx @@ -165,6 +165,8 @@ export function MessageListView({ [sharedT] ) + const sessionSyncState = session?.syncState ?? "idle" + const { threadItems, nonStreamingAdapted } = useMemo(() => { const allTurns = timelineTurns.map((item) => item.turn) const allAdapted = adaptMessageTurns(allTurns, adapterText) @@ -206,12 +208,15 @@ export function MessageListView({ } const lastPhase = timelineTurns[timelineTurns.length - 1]?.phase ?? null - if (connStatus === "prompting" && lastPhase === "optimistic") { + if ( + lastPhase === "optimistic" && + (connStatus === "prompting" || sessionSyncState === "awaiting_persist") + ) { items.push({ key: "pending-typing", kind: "typing" }) } return { threadItems: items, nonStreamingAdapted: nonStreaming } - }, [adapterText, connStatus, timelineTurns]) + }, [adapterText, connStatus, sessionSyncState, timelineTurns]) const historicalPlanEntries = useMemo( () => extractLatestPlanEntriesFromMessages(nonStreamingAdapted), diff --git a/src/contexts/conversation-runtime-context.tsx b/src/contexts/conversation-runtime-context.tsx index dba9d0c..245be44 100644 --- a/src/contexts/conversation-runtime-context.tsx +++ b/src/contexts/conversation-runtime-context.tsx @@ -250,7 +250,10 @@ function reduceHydrateDetail( ...(current ?? createEmptySession(conversationId)), externalId: nextExternalId, persistedTurns, - liveMessage: hasPersistedAdvance ? null : (current?.liveMessage ?? null), + liveMessage: + hasPersistedAdvance && current?.syncState !== "awaiting_persist" + ? null + : (current?.liveMessage ?? null), optimisticTurns: shouldDropOptimistic ? [] : optimisticTurns, syncState: shouldDropOptimistic ? "idle" : (current?.syncState ?? "idle"), activeTurnToken: shouldDropOptimistic