diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts index 7dc6583b133..633fb5d6bef 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts @@ -382,6 +382,60 @@ describe("computeStableMessagesTimelineRows", () => { expect(repeated.result).toBe(initial.result); }); + it("reuses work rows when equivalent timeline derivations create new grouped arrays", () => { + const firstWorkEntry = { + id: "work-1", + createdAt: "2026-01-01T00:00:00Z", + label: "thinking", + detail: "Inspecting repository state", + tone: "thinking" as const, + }; + const secondWorkEntry = { + id: "work-2", + createdAt: "2026-01-01T00:00:01Z", + label: "read", + detail: "Reading package.json", + tone: "tool" as const, + }; + + const createRows = () => + deriveMessagesTimelineRows({ + timelineEntries: [ + { + id: "entry-work-1", + kind: "work", + createdAt: firstWorkEntry.createdAt, + entry: firstWorkEntry, + }, + { + id: "entry-work-2", + kind: "work", + createdAt: secondWorkEntry.createdAt, + entry: secondWorkEntry, + }, + ], + completionDividerBeforeEntryId: null, + isWorking: false, + activeTurnStartedAt: null, + turnDiffSummaryByAssistantMessageId: new Map(), + revertTurnCountByUserMessageId: new Map(), + }); + + const firstRows = createRows(); + const initial = computeStableMessagesTimelineRows(firstRows, { + byId: new Map(), + result: [], + }); + const secondRows = createRows(); + + expect(secondRows[0]).not.toBe(firstRows[0]); + + const repeated = computeStableMessagesTimelineRows(secondRows, initial); + + expect(repeated).toBe(initial); + expect(repeated.result[0]).toBe(initial.result[0]); + }); + it("returns a new result when row order changes without content changes", () => { const firstUserMessage = { id: "user-1" as never, diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index a56643d84b0..c99f0b98a66 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -1,3 +1,4 @@ +import * as Equal from "effect/Equal"; import { type TimelineEntry, type WorkLogEntry } from "../../session-logic"; import { type ChatMessage, type ProposedPlan, type TurnDiffSummary } from "../../types"; import { type MessageId } from "@t3tools/contracts"; @@ -223,7 +224,7 @@ function isRowUnchanged(a: MessagesTimelineRow, b: MessagesTimelineRow): boolean return a.proposedPlan === (b as typeof a).proposedPlan; case "work": - return a.groupedEntries === (b as typeof a).groupedEntries; + return Equal.equals(a.groupedEntries, (b as typeof a).groupedEntries); case "message": { const bm = b as typeof a;