diff --git a/packages/ui/src/components/chat/MessageList.tsx b/packages/ui/src/components/chat/MessageList.tsx index 7e621494..63717495 100644 --- a/packages/ui/src/components/chat/MessageList.tsx +++ b/packages/ui/src/components/chat/MessageList.tsx @@ -16,7 +16,7 @@ import { hasPendingUserSendAnimation, consumePendingUserSendAnimation } from '@/ import { streamPerfCount, streamPerfMeasure } from '@/stores/utils/streamDebug'; import type { StreamPhase } from './message/types'; -const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 15; +const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = Number.POSITIVE_INFINITY; const MESSAGE_LIST_OVERSCAN = 6; const estimateHistoryEntryHeight = (entry: RenderEntry | undefined): number => { diff --git a/packages/ui/src/sync/sync-context.tsx b/packages/ui/src/sync/sync-context.tsx index 4f1eca59..3a76dc2b 100644 --- a/packages/ui/src/sync/sync-context.tsx +++ b/packages/ui/src/sync/sync-context.tsx @@ -36,6 +36,7 @@ import type { SessionStatus } from "@opencode-ai/sdk/v2/client" import type { PermissionRequest } from "@/types/permission" import type { QuestionRequest } from "@/types/question" import * as sessionActions from "./session-actions" +import { mergeMessages } from "./optimistic" // --------------------------------------------------------------------------- // Context @@ -162,32 +163,6 @@ function haveEquivalentPartSnapshots(left: Part[] | undefined, right: Part[]): b return true } -function haveEquivalentMessageSnapshots(left: Message[] | undefined, right: Message[]): boolean { - if (!left) { - return right.length === 0 - } - - if (left.length !== right.length) { - return false - } - - for (let index = 0; index < left.length; index += 1) { - const leftMessage = left[index] - const rightMessage = right[index] - if (!leftMessage || !rightMessage) { - return false - } - if (leftMessage.id !== rightMessage.id) { - return false - } - if (syncSnapshotSignature(leftMessage) !== syncSnapshotSignature(rightMessage)) { - return false - } - } - - return true -} - function haveEquivalentSyncSnapshots(left: unknown, right: unknown): boolean { return syncSnapshotSignature(left) === syncSnapshotSignature(right) } @@ -813,14 +788,15 @@ async function resyncDirectoryAfterReconnect( } } - const messagesChanged = !haveEquivalentMessageSnapshots(state.message[sessionId], nextMessages) + const mergedMessages = mergeMessages(state.message[sessionId] ?? [], nextMessages) + const messagesChanged = mergedMessages !== (state.message[sessionId] ?? []) if (!sessionChanged && !messagesChanged && !partsChanged) { return state } return { ...(sessionChanged ? { session: sessions, sessionTotal } : {}), - ...(messagesChanged ? { message: { ...state.message, [sessionId]: nextMessages } } : {}), + ...(messagesChanged ? { message: { ...state.message, [sessionId]: mergedMessages } } : {}), ...(partsChanged ? { part: nextPartState } : {}), } }) @@ -2001,10 +1977,14 @@ export function useEnsureSessionMessages(sessionID: string, directory?: string) .sort((a: Part, b: Part) => cmp(a.id, b.id)) } - store.setState((state: DirectoryStore) => ({ - message: { ...state.message, [sessionID]: nextMessages }, - part: { ...state.part, ...nextPartState }, - })) + store.setState((state: DirectoryStore) => { + const currentMessages = state.message[sessionID] ?? [] + const mergedMessages = mergeMessages(currentMessages, nextMessages) + return { + message: mergedMessages !== currentMessages ? { ...state.message, [sessionID]: mergedMessages } : state.message, + part: { ...state.part, ...nextPartState }, + } + }) } catch { // Transient failure — next navigation or reconnect will retry } finally { diff --git a/packages/ui/src/sync/use-sync.ts b/packages/ui/src/sync/use-sync.ts index eda18483..70d31154 100644 --- a/packages/ui/src/sync/use-sync.ts +++ b/packages/ui/src/sync/use-sync.ts @@ -222,10 +222,10 @@ export function useSync() { } const current = store.getState() - const cached = options?.mode === "prepend" ? (current.message[sessionID] ?? []) : [] + const cached = current.message[sessionID] ?? [] const messages = options?.mode === "prepend" ? mergeMessages(cached, merged.session) - : merged.session + : (cached.length > 0 ? mergeMessages(cached, merged.session) : merged.session) // Build part updates — preserve existing references on prepend to avoid flicker const isPrepend = options?.mode === "prepend"