authorgravatar for 206951365+cursor[bot]@users.noreply.github.comcursor[bot] <206951365+cursor[bot]@users.noreply.github.com> 2026-05-05 20:57:33-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-05 20:57:33-07:00
log1498335e398f2cd23b8fd1133ca3ba020d9b8905
treea04f1702690e60f6f9e115269dc3228ff565a982
parent25c9d267acbf058229a07e9f789be8ac00dec6d7
signature Signed by PGP key B5690EEEBB952194

Optimize MessagesTimeline work row stability (#2527)

Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>

2 files changed, 56 insertions(+), 1 deletions(-)

apps/web/src/components/chat/MessagesTimeline.logic.test.ts+54
......@@ -382,6 +382,60 @@ describe("computeStableMessagesTimelineRows", () => {
382382 expect(repeated.result).toBe(initial.result);
383383 });
384384
385 it("reuses work rows when equivalent timeline derivations create new grouped arrays", () => {
386 const firstWorkEntry = {
387 id: "work-1",
388 createdAt: "2026-01-01T00:00:00Z",
389 label: "thinking",
390 detail: "Inspecting repository state",
391 tone: "thinking" as const,
392 };
393 const secondWorkEntry = {
394 id: "work-2",
395 createdAt: "2026-01-01T00:00:01Z",
396 label: "read",
397 detail: "Reading package.json",
398 tone: "tool" as const,
399 };
400
401 const createRows = () =>
402 deriveMessagesTimelineRows({
403 timelineEntries: [
404 {
405 id: "entry-work-1",
406 kind: "work",
407 createdAt: firstWorkEntry.createdAt,
408 entry: firstWorkEntry,
409 },
410 {
411 id: "entry-work-2",
412 kind: "work",
413 createdAt: secondWorkEntry.createdAt,
414 entry: secondWorkEntry,
415 },
416 ],
417 completionDividerBeforeEntryId: null,
418 isWorking: false,
419 activeTurnStartedAt: null,
420 turnDiffSummaryByAssistantMessageId: new Map(),
421 revertTurnCountByUserMessageId: new Map(),
422 });
423
424 const firstRows = createRows();
425 const initial = computeStableMessagesTimelineRows(firstRows, {
426 byId: new Map(),
427 result: [],
428 });
429 const secondRows = createRows();
430
431 expect(secondRows[0]).not.toBe(firstRows[0]);
432
433 const repeated = computeStableMessagesTimelineRows(secondRows, initial);
434
435 expect(repeated).toBe(initial);
436 expect(repeated.result[0]).toBe(initial.result[0]);
437 });
438
385439 it("returns a new result when row order changes without content changes", () => {
386440 const firstUserMessage = {
387441 id: "user-1" as never,
apps/web/src/components/chat/MessagesTimeline.logic.ts+2-1
......@@ -1,3 +1,4 @@
1import * as Equal from "effect/Equal";
12import { type TimelineEntry, type WorkLogEntry } from "../../session-logic";
23import { type ChatMessage, type ProposedPlan, type TurnDiffSummary } from "../../types";
34import { type MessageId } from "@t3tools/contracts";
......@@ -223,7 +224,7 @@ function isRowUnchanged(a: MessagesTimelineRow, b: MessagesTimelineRow): boolean
223224 return a.proposedPlan === (b as typeof a).proposedPlan;
224225
225226 case "work":
226 return a.groupedEntries === (b as typeof a).groupedEntries;
227 return Equal.equals(a.groupedEntries, (b as typeof a).groupedEntries);
227228
228229 case "message": {
229230 const bm = b as typeof a;