diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts index b0436875c2a330cdfe37bd93607e0973779f6293..94727086f1868769e1cfa7c6530c91f512db2937 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts @@ -1170,11 +1170,10 @@ describe("deriveMessagesTimelineRows", () => { })), ]; - const rows = deriveMessagesTimelineRows({ - timelineEntries, + const input = { latestTurn: { turnId, - state: "error", + state: "error" as const, startedAt: "2026-01-01T00:00:00Z", completedAt: "2026-01-01T00:00:10Z", }, @@ -1182,7 +1181,8 @@ describe("deriveMessagesTimelineRows", () => { activeTurnStartedAt: null, turnDiffSummaryByAssistantMessageId: new Map(), revertTurnCountByUserMessageId: new Map(), - }); + }; + const rows = deriveMessagesTimelineRows({ ...input, timelineEntries }); expect(rows.map((row) => row.id)).toEqual([ "turn-fold:turn-1", @@ -1205,6 +1205,11 @@ describe("deriveMessagesTimelineRows", () => { showAssistantMeta: false, showAssistantCopyButton: false, }); + expect( + deriveMessagesTimelineRows({ ...input, timelineEntries: timelineEntries.slice(0, 3) }).map( + (row) => row.id, + ), + ).toEqual(["turn-fold:turn-1", "assistant-final-entry"]); }); it("folds all assistant messages before the terminal message", () => { diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index 7b01caf16d0976d007b71f4eea52c580abf82d17..36c90c89563cce79c4ecc52e45f334f82b749c68 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -542,8 +542,8 @@ function workEntryIsActiveTurnActivity(entry: WorkLogEntry): boolean { /** * Settled turns fold activity before their terminal assistant message behind - * a "Worked for ..." row. Work that lands after that message stays visible so - * failed or interrupted turns do not hide their trailing tool-call summary. + * a "Worked for ..." row. A single ordinary activity after that message joins + * the fold, while larger groups and failures stay visible as a trailing summary. */ function deriveTurnFolds(input: { timelineEntries: ReadonlyArray; @@ -623,7 +623,11 @@ function deriveTurnFolds(input: { } const isCompaction = entry.kind === "work" && entry.entry.sourceActivityKind === "context-compaction"; - if (!isCompaction && index > terminalEntryIndex) { + const isSingleTrailingActivity = + group.entries.length === terminalEntryIndex + 2 && + entry.kind === "work" && + !workEntryDisplayIndicatesToolFailure(entry.entry); + if (!isCompaction && index > terminalEntryIndex && !isSingleTrailingActivity) { continue; } // Agent-spawn CTA rows never fold: workflows outlive their launching diff --git a/apps/web/src/components/chat/MessagesTimeline.test.tsx b/apps/web/src/components/chat/MessagesTimeline.test.tsx index 7cb64023d0e4c9ebf716ff87e448bf524a18f67c..ccf9be69a47177beda3d623c65abe9e33fcb4bc3 100644 --- a/apps/web/src/components/chat/MessagesTimeline.test.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.test.tsx @@ -1135,7 +1135,7 @@ describe("MessagesTimeline", () => { label: "Ran command", tone: "tool", itemType: "command_execution", - toolLifecycleStatus: "completed", + toolLifecycleStatus: "failed", }, }, ]}