import { describe, expect, it } from "vite-plus/test";import { CheckpointRef, EnvironmentId, EventId, MessageId, ProjectId, ProviderInstanceId, ThreadId, TurnId, type OrchestrationThread,} from "@t3tools/contracts";import { applyThreadDetailEvent, createEnvironmentThreadDetailAtoms, EMPTY_ENVIRONMENT_THREAD_STATE,} from "@t3tools/client-runtime/state/threads";import * as Option from "effect/Option";import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity";import { computeStableMessagesTimelineRows, computeMessageDurationStart, deriveMessagesTimelineRows, deriveMessagesTimelineRowsWithState, liveWorkEntryLabel, normalizeCompactToolLabel, resolveAssistantMessageCopyState, resolveWorkGroupScrollIndex, shouldFollowWorkGroupAppend, shouldPreserveAssistantLineBreaks, type MessagesTimelineRow, type MessagesTimelineRowsProjection, workEntryDisplayLabel,} from "./MessagesTimeline.logic";import { createMessageAttachmentPreviewProjector, deriveTimelineEntries, deriveTimelineEntriesWithState, type WorkLogEntry, type TimelineEntriesProjection,} from "../../session-logic";import { isImageAttachment, type ChatMessage, type TurnDiffSummary } from "../../types"; describe("streaming row projection", () => { function fixture(text = "") { const turnId = TurnId.make("live-turn"); const historyTurnId = TurnId.make("history-turn"); const time = (second: number) => new Date(Date.UTC(2026, 8, 4, 0, 0, second)).toISOString(); const messages: ChatMessage[] = [ { id: MessageId.make("history-user"), role: "user", text: "Inspect", turnId: null, createdAt: time(0), updatedAt: time(0), streaming: false, }, { id: MessageId.make("history-assistant"), role: "assistant", text: "Done", turnId: historyTurnId, createdAt: time(3), updatedAt: time(4), streaming: false, }, { id: MessageId.make("live-user"), role: "user", text: "Continue", turnId: null, createdAt: time(5), updatedAt: time(5), streaming: false, }, { id: MessageId.make("live-assistant"), role: "assistant", text, turnId, createdAt: time(7), updatedAt: time(7), streaming: true, }, ]; const work: WorkLogEntry[] = [ { id: "history-work", turnId: historyTurnId, createdAt: time(1), label: "Ran command", tone: "tool", command: "vp test", toolCallId: "history-tool", toolLifecycleStatus: "failed", sourceActivityKind: "tool.completed", }, { id: "live-work", turnId, createdAt: time(6), label: "Read file", tone: "tool", changedFiles: ["/repo/src/index.ts"], toolCallId: "live-tool", toolLifecycleStatus: "completed", sourceActivityKind: "tool.completed", }, ]; const timeline = deriveTimelineEntriesWithState(messages, [], work); const input = { timelineEntries: timeline.entries, latestTurn: { turnId, state: "running", startedAt: time(5), completedAt: null }, runningTurnId: turnId, isWorking: true, activeTurnStartedAt: time(5), turnDiffSummaries: [], supportsConversationRollback: false, } satisfies Parameters<typeof deriveMessagesTimelineRows>[0]; return { messages, work, timeline, input, time, turnId, historyTurnId }; } it.each([ ["", "Now visible"], [" \n", "Now visible"], ["Visible", ""], ["", " \t\n"], ["Visible", "★ Insight\nKeep these line breaks"], ])("keeps row parity and history identity when text changes from %j to %j", (before, after) => { const initial = fixture(before); const previous = deriveMessagesTimelineRowsWithState(initial.input); Object.freeze(previous.rows); for (const row of previous.rows) Object.freeze(row); const last = initial.messages.at(-1)!; const messages = [ ...initial.messages.slice(0, -1), { ...last, text: after, updatedAt: initial.time(8) }, ]; const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); const input = { ...initial.input, timelineEntries: timeline.entries }; const next = deriveMessagesTimelineRowsWithState(input, previous); expect(next.rows).toEqual( deriveMessagesTimelineRows({ ...input, timelineEntries: deriveTimelineEntries(messages, [], initial.work), }), ); for (const [index, row] of previous.rows.entries()) { if ((row.kind === "message" || row.kind === "assistant-meta") && row.message === last) { expect(next.rows[index]).toMatchObject({ message: { text: after } }); expect(row.message.text).toBe(before); } else { expect(next.rows[index]).toBe(row); } } }); it("keeps earlier emitted rows unchanged across two projection branches", () => { const initial = fixture(); const previous = deriveMessagesTimelineRowsWithState(initial.input); const last = initial.messages.at(-1)!; const branch = (text: string) => { const messages = [...initial.messages.slice(0, -1), { ...last, text }]; const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); const input = { ...initial.input, timelineEntries: timeline.entries }; const projection = deriveMessagesTimelineRowsWithState(input, previous); expect(projection.rows).toEqual(deriveMessagesTimelineRows(input)); return projection; }; const first = branch("First branch"); const savedFirst = structuredClone(first.rows); branch("Second branch"); expect(first.rows).toEqual(savedFirst); expect(previous.rows).toEqual(deriveMessagesTimelineRows(initial.input)); }); it.each(["file", "image", "handoff", "streaming-image"] as const)( "preserves stream reuse through the real %s preview materializer", (kind) => { const initial = fixture("Partial"); const image = { type: "image" as const, id: "image", name: "image.png", mimeType: "image/png", sizeBytes: 42, }; const file = { type: "file" as const, id: "file", name: "file.txt", mimeType: "text/plain", sizeBytes: 8, }; const source = initial.messages.map((message, index) => index === 0 || (kind === "streaming-image" && index === initial.messages.length - 1) ? { ...message, attachments: kind === "file" ? [file] : [image, file] } : message, ); const server = createMessageAttachmentPreviewProjector(); const handoff = createMessageAttachmentPreviewProjector(); const materialize = (messages: ReadonlyArray<ChatMessage>, url: string) => { const urls = new Map(kind === "handoff" ? [] : [[image.id, url]]); return messages.map((message) => { const displayed = server(message, (attachment) => urls.get(attachment.id)); return kind === "handoff" && message.role === "user" ? handoff(displayed, (attachment) => isImageAttachment(attachment) ? "blob:handoff" : undefined, ) : displayed; }); }; const firstMessages = materialize(source, "https://first.test/image"); const firstTimeline = deriveTimelineEntriesWithState(firstMessages, [], initial.work); const input = { ...initial.input, timelineEntries: firstTimeline.entries }; const previous = deriveMessagesTimelineRowsWithState(input); const saved = structuredClone(previous.rows); const nextSource = [...source.slice(0, -1), { ...source.at(-1)!, text: "Next token" }]; const nextMessages = materialize(nextSource, "https://first.test/image"); const nextTimeline = deriveTimelineEntriesWithState( nextMessages, [], initial.work, firstTimeline, ); const nextInput = { ...input, timelineEntries: nextTimeline.entries }; const next = deriveMessagesTimelineRowsWithState(nextInput, previous); expect(nextMessages[0]).toBe(firstMessages[0]); expect(next.rows).toEqual(deriveMessagesTimelineRows(nextInput)); for (const [index, row] of previous.rows.entries()) { if ( (row.kind === "message" || row.kind === "assistant-meta") && row.message.id === source.at(-1)?.id ) continue; expect(next.rows[index]).toBe(row); } expect(previous.rows).toEqual(saved); if (kind === "image" || kind === "streaming-image") { const renewed = materialize(nextSource, "https://renewed.test/image"); const renewedTimeline = deriveTimelineEntriesWithState( renewed, [], initial.work, nextTimeline, ); const renewedInput = { ...input, timelineEntries: renewedTimeline.entries }; const renewedRows = deriveMessagesTimelineRowsWithState(renewedInput, next); expect(renewedRows.rows).toEqual(deriveMessagesTimelineRows(renewedInput)); expect(renewed[0]?.attachments?.[0]).toMatchObject({ previewUrl: "https://renewed.test/image", }); expect(firstMessages[0]?.attachments?.[0]).toMatchObject({ previewUrl: "https://first.test/image", }); } }, ); it("owns checkpoint lookups across streaming and equal source snapshots", () => { const initial = fixture("Partial"); let checkpointLookupReads = 0; const summary: TurnDiffSummary = { turnId: initial.historyTurnId, get checkpointTurnCount() { checkpointLookupReads += 1; return 1; }, checkpointRef: CheckpointRef.make("refs/t3/checkpoints/history-turn"), status: "ready", files: [], get assistantMessageId() { checkpointLookupReads += 1; return MessageId.make("history-assistant"); }, get completedAt() { checkpointLookupReads += 1; return initial.time(4); }, }; const input = { ...initial.input, turnDiffSummaries: [summary], supportsConversationRollback: true, expandedTurnIds: new Set([initial.historyTurnId]), expandedWorkGroupIds: new Set<string>(), }; const previous = deriveMessagesTimelineRowsWithState(input); expect(checkpointLookupReads).toBeGreaterThan(0); expect(previous.rows.some((row) => row.kind === "message" && row.revertTurnCount === 0)).toBe( true, ); const last = initial.messages.at(-1)!; const messages = [...initial.messages.slice(0, -1), { ...last, text: "Partial token" }]; const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); const nextInput = { ...input, timelineEntries: timeline.entries, turnDiffSummaries: [...input.turnDiffSummaries], latestTurn: { ...input.latestTurn }, expandedTurnIds: new Set(input.expandedTurnIds), expandedWorkGroupIds: new Set(input.expandedWorkGroupIds), }; checkpointLookupReads = 0; const next = deriveMessagesTimelineRowsWithState(nextInput, previous); expect(checkpointLookupReads).toBe(0); expect(next.rows).toEqual(deriveMessagesTimelineRows(nextInput)); for (const [index, row] of previous.rows.entries()) { if ((row.kind === "message" || row.kind === "assistant-meta") && row.message === last) { expect(next.rows[index]).toMatchObject({ message: { text: "Partial token" } }); } else { expect(next.rows[index]).toBe(row); } } const changed = deriveMessagesTimelineRowsWithState( { ...nextInput, turnDiffSummaries: [{ ...summary, checkpointTurnCount: 3 }] }, next, ); expect( changed.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), ).toMatchObject({ revertTurnCount: 2 }); const unsupported = deriveMessagesTimelineRowsWithState( { ...changed.input, supportsConversationRollback: false }, changed, ); expect( unsupported.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), ).toMatchObject({ revertTurnCount: undefined }); expect( previous.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), ).toMatchObject({ revertTurnCount: 0 }); }); it("reuses long-thread rows through detail events, selectors, and attachment previews", () => { const initial = fixture("Partial"); let checkpointLookupReads = 0; const history = Array.from({ length: 250 }, (_, index) => { const turnId = TurnId.make(`older-turn-${index}`); const user = { ...initial.messages[0]!, id: MessageId.make(`older-user-${index}`), createdAt: new Date(Date.UTC(2026, 8, 3, 0, 0, index * 5)).toISOString(), attachments: [ { type: "image" as const, id: "image", name: "image.png", mimeType: "image/png", sizeBytes: 42, }, ], }; const assistant = { ...initial.messages[1]!, id: MessageId.make(`older-assistant-${index}`), turnId, createdAt: new Date(Date.UTC(2026, 8, 3, 0, 0, index * 5 + 3)).toISOString(), }; const checkpoint: TurnDiffSummary = { turnId, get checkpointTurnCount() { checkpointLookupReads += 1; return index + 1; }, checkpointRef: CheckpointRef.make(`refs/t3/checkpoints/older-${index}`), status: "ready", files: [], get assistantMessageId() { checkpointLookupReads += 1; return assistant.id; }, get completedAt() { checkpointLookupReads += 1; return assistant.createdAt; }, }; return { user, assistant, checkpoint }; }); const liveMessage = initial.messages.at(-1)!; let thread: OrchestrationThread = { id: ThreadId.make("streaming-thread"), projectId: ProjectId.make("project"), title: "Long thread", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, runtimeMode: "full-access", interactionMode: "default", branch: null, pullRequests: [], worktreePath: null, latestTurn: { ...initial.input.latestTurn, requestedAt: initial.time(5), assistantMessageId: liveMessage.id, }, createdAt: initial.time(0), updatedAt: initial.time(7), archivedAt: null, settledOverride: null, settledAt: null, deletedAt: null, messages: [ ...history.flatMap(({ user, assistant }) => [user, assistant]), ...initial.messages, ], proposedPlans: [], activities: [], checkpoints: history.map(({ checkpoint }) => checkpoint), session: null, }; const state = Atom.make( AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(thread) }), ); const details = createEnvironmentThreadDetailAtoms(() => state); const ref = { environmentId: EnvironmentId.make("local"), threadId: thread.id }; const registry = AtomRegistry.make(); const unmount = registry.mount(details.detailAtom(ref)); const preview = createMessageAttachmentPreviewProjector(); let imageUrl = "https://first.test/image"; let timeline: TimelineEntriesProjection | null = null; let projection: MessagesTimelineRowsProjection | null = null; const project = () => { const selected = registry.get(details.detailAtom(ref)); if (selected === null) throw new Error("Missing thread detail"); const messages = selected.messages.map((message) => preview(message, () => imageUrl)); timeline = deriveTimelineEntriesWithState( messages, selected.proposedPlans, initial.work, timeline, ); projection = deriveMessagesTimelineRowsWithState( { timelineEntries: timeline.entries, latestTurn: selected.latestTurn, runningTurnId: selected.latestTurn?.state === "running" ? selected.latestTurn.turnId : null, isWorking: selected.latestTurn?.state === "running", activeTurnStartedAt: selected.latestTurn?.startedAt ?? null, turnDiffSummaries: selected.checkpoints, supportsConversationRollback: true, }, projection, ); return projection; }; const send = (text: string, sequence: number, streaming = true) => { const result = applyThreadDetailEvent(thread, { eventId: EventId.make(`delta-${sequence}`), sequence, commandId: null, causationEventId: null, correlationId: null, metadata: {}, occurredAt: initial.time(8 + sequence), aggregateKind: "thread", aggregateId: thread.id, type: "thread.message-sent", payload: { threadId: thread.id, messageId: liveMessage.id, role: "assistant", text, turnId: initial.turnId, streaming, createdAt: liveMessage.createdAt, updatedAt: initial.time(8 + sequence), }, }); if (result.kind !== "updated") throw new Error("Message event did not update the thread"); thread = result.thread; registry.set( state, AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(thread) }), ); return project(); }; try { const first = project(); const saved = structuredClone(first.rows); expect(checkpointLookupReads).toBeGreaterThan(0); checkpointLookupReads = 0; for (let index = 0; index < 10; index += 1) { const next = send(` ${index}`, index + 1); for (const [rowIndex, row] of first.rows.entries()) { if ( (row.kind === "message" || row.kind === "assistant-meta") && row.message.id === liveMessage.id ) continue; expect(next.rows[rowIndex]).toBe(row); } } expect(checkpointLookupReads).toBe(0); const streamed = project();Lines 1–500 of 3071. Load more
import { describe, expect, it } from "vite-plus/test"; import { CheckpointRef, EnvironmentId, EventId, MessageId, ProjectId, ProviderInstanceId, ThreadId, TurnId, type OrchestrationThread, } from "@t3tools/contracts"; import { applyThreadDetailEvent, createEnvironmentThreadDetailAtoms, EMPTY_ENVIRONMENT_THREAD_STATE, } from "@t3tools/client-runtime/state/threads"; import * as Option from "effect/Option"; import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; import { computeStableMessagesTimelineRows, computeMessageDurationStart, deriveMessagesTimelineRows, deriveMessagesTimelineRowsWithState, liveWorkEntryLabel, normalizeCompactToolLabel, resolveAssistantMessageCopyState, resolveWorkGroupScrollIndex, shouldFollowWorkGroupAppend, shouldPreserveAssistantLineBreaks, type MessagesTimelineRow, type MessagesTimelineRowsProjection, workEntryDisplayLabel, } from "./MessagesTimeline.logic"; import { createMessageAttachmentPreviewProjector, deriveTimelineEntries, deriveTimelineEntriesWithState, type WorkLogEntry, type TimelineEntriesProjection, } from "../../session-logic"; import { isImageAttachment, type ChatMessage, type TurnDiffSummary } from "../../types"; describe("streaming row projection", () => { function fixture(text = "") { const turnId = TurnId.make("live-turn"); const historyTurnId = TurnId.make("history-turn"); const time = (second: number) => new Date(Date.UTC(2026, 8, 4, 0, 0, second)).toISOString(); const messages: ChatMessage[] = [ { id: MessageId.make("history-user"), role: "user", text: "Inspect", turnId: null, createdAt: time(0), updatedAt: time(0), streaming: false, }, { id: MessageId.make("history-assistant"), role: "assistant", text: "Done", turnId: historyTurnId, createdAt: time(3), updatedAt: time(4), streaming: false, }, { id: MessageId.make("live-user"), role: "user", text: "Continue", turnId: null, createdAt: time(5), updatedAt: time(5), streaming: false, }, { id: MessageId.make("live-assistant"), role: "assistant", text, turnId, createdAt: time(7), updatedAt: time(7), streaming: true, }, ]; const work: WorkLogEntry[] = [ { id: "history-work", turnId: historyTurnId, createdAt: time(1), label: "Ran command", tone: "tool", command: "vp test", toolCallId: "history-tool", toolLifecycleStatus: "failed", sourceActivityKind: "tool.completed", }, { id: "live-work", turnId, createdAt: time(6), label: "Read file", tone: "tool", changedFiles: ["/repo/src/index.ts"], toolCallId: "live-tool", toolLifecycleStatus: "completed", sourceActivityKind: "tool.completed", }, ]; const timeline = deriveTimelineEntriesWithState(messages, [], work); const input = { timelineEntries: timeline.entries, latestTurn: { turnId, state: "running", startedAt: time(5), completedAt: null }, runningTurnId: turnId, isWorking: true, activeTurnStartedAt: time(5), turnDiffSummaries: [], supportsConversationRollback: false, } satisfies Parameters<typeof deriveMessagesTimelineRows>[0]; return { messages, work, timeline, input, time, turnId, historyTurnId }; } it.each([ ["", "Now visible"], [" \n", "Now visible"], ["Visible", ""], ["", " \t\n"], ["Visible", "★ Insight\nKeep these line breaks"], ])("keeps row parity and history identity when text changes from %j to %j", (before, after) => { const initial = fixture(before); const previous = deriveMessagesTimelineRowsWithState(initial.input); Object.freeze(previous.rows); for (const row of previous.rows) Object.freeze(row); const last = initial.messages.at(-1)!; const messages = [ ...initial.messages.slice(0, -1), { ...last, text: after, updatedAt: initial.time(8) }, ]; const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); const input = { ...initial.input, timelineEntries: timeline.entries }; const next = deriveMessagesTimelineRowsWithState(input, previous); expect(next.rows).toEqual( deriveMessagesTimelineRows({ ...input, timelineEntries: deriveTimelineEntries(messages, [], initial.work), }), ); for (const [index, row] of previous.rows.entries()) { if ((row.kind === "message" || row.kind === "assistant-meta") && row.message === last) { expect(next.rows[index]).toMatchObject({ message: { text: after } }); expect(row.message.text).toBe(before); } else { expect(next.rows[index]).toBe(row); } } }); it("keeps earlier emitted rows unchanged across two projection branches", () => { const initial = fixture(); const previous = deriveMessagesTimelineRowsWithState(initial.input); const last = initial.messages.at(-1)!; const branch = (text: string) => { const messages = [...initial.messages.slice(0, -1), { ...last, text }]; const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); const input = { ...initial.input, timelineEntries: timeline.entries }; const projection = deriveMessagesTimelineRowsWithState(input, previous); expect(projection.rows).toEqual(deriveMessagesTimelineRows(input)); return projection; }; const first = branch("First branch"); const savedFirst = structuredClone(first.rows); branch("Second branch"); expect(first.rows).toEqual(savedFirst); expect(previous.rows).toEqual(deriveMessagesTimelineRows(initial.input)); }); it.each(["file", "image", "handoff", "streaming-image"] as const)( "preserves stream reuse through the real %s preview materializer", (kind) => { const initial = fixture("Partial"); const image = { type: "image" as const, id: "image", name: "image.png", mimeType: "image/png", sizeBytes: 42, }; const file = { type: "file" as const, id: "file", name: "file.txt", mimeType: "text/plain", sizeBytes: 8, }; const source = initial.messages.map((message, index) => index === 0 || (kind === "streaming-image" && index === initial.messages.length - 1) ? { ...message, attachments: kind === "file" ? [file] : [image, file] } : message, ); const server = createMessageAttachmentPreviewProjector(); const handoff = createMessageAttachmentPreviewProjector(); const materialize = (messages: ReadonlyArray<ChatMessage>, url: string) => { const urls = new Map(kind === "handoff" ? [] : [[image.id, url]]); return messages.map((message) => { const displayed = server(message, (attachment) => urls.get(attachment.id)); return kind === "handoff" && message.role === "user" ? handoff(displayed, (attachment) => isImageAttachment(attachment) ? "blob:handoff" : undefined, ) : displayed; }); }; const firstMessages = materialize(source, "https://first.test/image"); const firstTimeline = deriveTimelineEntriesWithState(firstMessages, [], initial.work); const input = { ...initial.input, timelineEntries: firstTimeline.entries }; const previous = deriveMessagesTimelineRowsWithState(input); const saved = structuredClone(previous.rows); const nextSource = [...source.slice(0, -1), { ...source.at(-1)!, text: "Next token" }]; const nextMessages = materialize(nextSource, "https://first.test/image"); const nextTimeline = deriveTimelineEntriesWithState( nextMessages, [], initial.work, firstTimeline, ); const nextInput = { ...input, timelineEntries: nextTimeline.entries }; const next = deriveMessagesTimelineRowsWithState(nextInput, previous); expect(nextMessages[0]).toBe(firstMessages[0]); expect(next.rows).toEqual(deriveMessagesTimelineRows(nextInput)); for (const [index, row] of previous.rows.entries()) { if ( (row.kind === "message" || row.kind === "assistant-meta") && row.message.id === source.at(-1)?.id ) continue; expect(next.rows[index]).toBe(row); } expect(previous.rows).toEqual(saved); if (kind === "image" || kind === "streaming-image") { const renewed = materialize(nextSource, "https://renewed.test/image"); const renewedTimeline = deriveTimelineEntriesWithState( renewed, [], initial.work, nextTimeline, ); const renewedInput = { ...input, timelineEntries: renewedTimeline.entries }; const renewedRows = deriveMessagesTimelineRowsWithState(renewedInput, next); expect(renewedRows.rows).toEqual(deriveMessagesTimelineRows(renewedInput)); expect(renewed[0]?.attachments?.[0]).toMatchObject({ previewUrl: "https://renewed.test/image", }); expect(firstMessages[0]?.attachments?.[0]).toMatchObject({ previewUrl: "https://first.test/image", }); } }, ); it("owns checkpoint lookups across streaming and equal source snapshots", () => { const initial = fixture("Partial"); let checkpointLookupReads = 0; const summary: TurnDiffSummary = { turnId: initial.historyTurnId, get checkpointTurnCount() { checkpointLookupReads += 1; return 1; }, checkpointRef: CheckpointRef.make("refs/t3/checkpoints/history-turn"), status: "ready", files: [], get assistantMessageId() { checkpointLookupReads += 1; return MessageId.make("history-assistant"); }, get completedAt() { checkpointLookupReads += 1; return initial.time(4); }, }; const input = { ...initial.input, turnDiffSummaries: [summary], supportsConversationRollback: true, expandedTurnIds: new Set([initial.historyTurnId]), expandedWorkGroupIds: new Set<string>(), }; const previous = deriveMessagesTimelineRowsWithState(input); expect(checkpointLookupReads).toBeGreaterThan(0); expect(previous.rows.some((row) => row.kind === "message" && row.revertTurnCount === 0)).toBe( true, ); const last = initial.messages.at(-1)!; const messages = [...initial.messages.slice(0, -1), { ...last, text: "Partial token" }]; const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); const nextInput = { ...input, timelineEntries: timeline.entries, turnDiffSummaries: [...input.turnDiffSummaries], latestTurn: { ...input.latestTurn }, expandedTurnIds: new Set(input.expandedTurnIds), expandedWorkGroupIds: new Set(input.expandedWorkGroupIds), }; checkpointLookupReads = 0; const next = deriveMessagesTimelineRowsWithState(nextInput, previous); expect(checkpointLookupReads).toBe(0); expect(next.rows).toEqual(deriveMessagesTimelineRows(nextInput)); for (const [index, row] of previous.rows.entries()) { if ((row.kind === "message" || row.kind === "assistant-meta") && row.message === last) { expect(next.rows[index]).toMatchObject({ message: { text: "Partial token" } }); } else { expect(next.rows[index]).toBe(row); } } const changed = deriveMessagesTimelineRowsWithState( { ...nextInput, turnDiffSummaries: [{ ...summary, checkpointTurnCount: 3 }] }, next, ); expect( changed.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), ).toMatchObject({ revertTurnCount: 2 }); const unsupported = deriveMessagesTimelineRowsWithState( { ...changed.input, supportsConversationRollback: false }, changed, ); expect( unsupported.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), ).toMatchObject({ revertTurnCount: undefined }); expect( previous.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), ).toMatchObject({ revertTurnCount: 0 }); }); it("reuses long-thread rows through detail events, selectors, and attachment previews", () => { const initial = fixture("Partial"); let checkpointLookupReads = 0; const history = Array.from({ length: 250 }, (_, index) => { const turnId = TurnId.make(`older-turn-${index}`); const user = { ...initial.messages[0]!, id: MessageId.make(`older-user-${index}`), createdAt: new Date(Date.UTC(2026, 8, 3, 0, 0, index * 5)).toISOString(), attachments: [ { type: "image" as const, id: "image", name: "image.png", mimeType: "image/png", sizeBytes: 42, }, ], }; const assistant = { ...initial.messages[1]!, id: MessageId.make(`older-assistant-${index}`), turnId, createdAt: new Date(Date.UTC(2026, 8, 3, 0, 0, index * 5 + 3)).toISOString(), }; const checkpoint: TurnDiffSummary = { turnId, get checkpointTurnCount() { checkpointLookupReads += 1; return index + 1; }, checkpointRef: CheckpointRef.make(`refs/t3/checkpoints/older-${index}`), status: "ready", files: [], get assistantMessageId() { checkpointLookupReads += 1; return assistant.id; }, get completedAt() { checkpointLookupReads += 1; return assistant.createdAt; }, }; return { user, assistant, checkpoint }; }); const liveMessage = initial.messages.at(-1)!; let thread: OrchestrationThread = { id: ThreadId.make("streaming-thread"), projectId: ProjectId.make("project"), title: "Long thread", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, runtimeMode: "full-access", interactionMode: "default", branch: null, pullRequests: [], worktreePath: null, latestTurn: { ...initial.input.latestTurn, requestedAt: initial.time(5), assistantMessageId: liveMessage.id, }, createdAt: initial.time(0), updatedAt: initial.time(7), archivedAt: null, settledOverride: null, settledAt: null, deletedAt: null, messages: [ ...history.flatMap(({ user, assistant }) => [user, assistant]), ...initial.messages, ], proposedPlans: [], activities: [], checkpoints: history.map(({ checkpoint }) => checkpoint), session: null, }; const state = Atom.make( AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(thread) }), ); const details = createEnvironmentThreadDetailAtoms(() => state); const ref = { environmentId: EnvironmentId.make("local"), threadId: thread.id }; const registry = AtomRegistry.make(); const unmount = registry.mount(details.detailAtom(ref)); const preview = createMessageAttachmentPreviewProjector(); let imageUrl = "https://first.test/image"; let timeline: TimelineEntriesProjection | null = null; let projection: MessagesTimelineRowsProjection | null = null; const project = () => { const selected = registry.get(details.detailAtom(ref)); if (selected === null) throw new Error("Missing thread detail"); const messages = selected.messages.map((message) => preview(message, () => imageUrl)); timeline = deriveTimelineEntriesWithState( messages, selected.proposedPlans, initial.work, timeline, ); projection = deriveMessagesTimelineRowsWithState( { timelineEntries: timeline.entries, latestTurn: selected.latestTurn, runningTurnId: selected.latestTurn?.state === "running" ? selected.latestTurn.turnId : null, isWorking: selected.latestTurn?.state === "running", activeTurnStartedAt: selected.latestTurn?.startedAt ?? null, turnDiffSummaries: selected.checkpoints, supportsConversationRollback: true, }, projection, ); return projection; }; const send = (text: string, sequence: number, streaming = true) => { const result = applyThreadDetailEvent(thread, { eventId: EventId.make(`delta-${sequence}`), sequence, commandId: null, causationEventId: null, correlationId: null, metadata: {}, occurredAt: initial.time(8 + sequence), aggregateKind: "thread", aggregateId: thread.id, type: "thread.message-sent", payload: { threadId: thread.id, messageId: liveMessage.id, role: "assistant", text, turnId: initial.turnId, streaming, createdAt: liveMessage.createdAt, updatedAt: initial.time(8 + sequence), }, }); if (result.kind !== "updated") throw new Error("Message event did not update the thread"); thread = result.thread; registry.set( state, AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(thread) }), ); return project(); }; try { const first = project(); const saved = structuredClone(first.rows); expect(checkpointLookupReads).toBeGreaterThan(0); checkpointLookupReads = 0; for (let index = 0; index < 10; index += 1) { const next = send(` ${index}`, index + 1); for (const [rowIndex, row] of first.rows.entries()) { if ( (row.kind === "message" || row.kind === "assistant-meta") && row.message.id === liveMessage.id ) continue; expect(next.rows[rowIndex]).toBe(row); } } expect(checkpointLookupReads).toBe(0); const streamed = project();