diff --git a/docs/architecture/agent-message-ux-catalog.md b/docs/architecture/agent-message-ux-catalog.md index 5dcc2442..ab44658e 100644 --- a/docs/architecture/agent-message-ux-catalog.md +++ b/docs/architecture/agent-message-ux-catalog.md @@ -40,6 +40,8 @@ The current column exercises these visible cases in one trace: - subagent progress and nested output - Todo progress - changed-file diff summary +- a Codex turn-start checkpoint whose expanded recovery control does not repeat + the accordion title - single-line system notices, rate-limit guidance, plugin installation, and a multi-line provider warning whose expanded body contains detail without repeating the row title @@ -118,7 +120,8 @@ construction even though they are not separate `NormalizedProviderEvent` types. such as `Context window at 62%` must not repeat the same sentence inside a nested body. For a multi-line generic system notice, the first non-empty line is the title and only the remaining lines belong in the expanded body; - compact checkpoints keep their specialized recovery surface. + compact and turn-start checkpoints keep their specialized recovery surface, + without repeating the title inside it. - Tool results update the existing tool row by id. A result must never create a second row that repeats the header or input. - Stave-owned MCP tools use the Stave icon and an action-oriented title. Only diff --git a/src/components/session/chat-panel-message-parts.tsx b/src/components/session/chat-panel-message-parts.tsx index f3b98fdc..7382b8fe 100644 --- a/src/components/session/chat-panel-message-parts.tsx +++ b/src/components/session/chat-panel-message-parts.tsx @@ -226,6 +226,7 @@ export function MessagePartRenderer(args: { const compactBoundaryTrigger = part.compactBoundary?.trigger ?? compactedMatch?.[1]; const compactBoundaryGitRef = part.compactBoundary?.gitRef; + const isTurnStartCheckpoint = compactBoundaryTrigger === "turn_start"; const handleRestoreCompactBoundary = () => { if (!compactBoundaryGitRef || isRestoringCompactBoundary) { return; @@ -241,10 +242,11 @@ export function MessagePartRenderer(args: { setIsRestoringCompactBoundary(false); }); }; - if (compactedMatch) { + if (part.compactBoundary != null || compactedMatch) { return ( { }); }); + test("does not repeat a Codex turn checkpoint inside its accordion", async () => { + const { AssistantMessageBody } = await loadAssistantMessageBodies(); + const checkpointNotice = "Checkpoint captured before Codex turn."; + const html = renderToStaticMarkup(createElement(AssistantMessageBody, { + message: createAssistantMessage({ + isStreaming: true, + parts: [{ + type: "system_event", + content: checkpointNotice, + compactBoundary: { trigger: "turn_start", gitRef: "abc123" }, + }], + }), + taskId: "task-1", + messageId: "message-1", + streamingEnabled: true, + })); + + expect(html.split(checkpointNotice).length - 1).toBe(1); + expect(html).toContain("Workspace checkpoint"); + expect(html).toContain("Restore"); + }); + test("uses Stave-specific copy and icon without rewriting external MCP names", async () => { const { AssistantMessageBody } = await loadAssistantMessageBodies(); const html = renderToStaticMarkup(createElement(AssistantMessageBody, {