From 1156bc6441ac1ef8b2e364f0c18d4fb03c9dbd19 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Fri, 28 Aug 2026 21:38:37 +0800 Subject: [PATCH 1/4] fix(ui): state an aborted turn's outcome after its timeline The aborted marker rendered at the top of the assistant answer, above the reasoning disclosures and tool calls, where it read as a header on work that had in fact all completed. PR #4073 moved the failed banner below the timeline for exactly this reason but left the aborted marker behind; both are terminal outcome markers and share the same slot, so move the aborted marker next to the failed banner and match its block rhythm. Generated-by: Maka --- packages/ui/src/chat-turn.tsx | 24 ++++++++++++------------ packages/ui/src/styles.css | 4 +--- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/chat-turn.tsx b/packages/ui/src/chat-turn.tsx index c2559e6241..c9e41c01a7 100644 --- a/packages/ui/src/chat-turn.tsx +++ b/packages/ui/src/chat-turn.tsx @@ -660,12 +660,6 @@ export const TurnView = memo(function TurnView(props: { className="maka-chat-message maka-assistant-answer" >
- {ownsTurnChrome && turn.status === 'aborted' && ( - - - )} {/* The turn timeline is the rendering source of truth (materialize.ts): each step's 深度思考 disclosure, answer bubble, and Astryx tool group in the order the model produced them. @@ -699,12 +693,18 @@ export const TurnView = memo(function TurnView(props: { /> ), )} - {/* A failed turn's banner states the OUTCOME, so it belongs after - the work it is the outcome of. It used to render above the - timeline, where it read as a header on reasoning and tool - calls that had in fact all succeeded. - - `description` carries the parked-resume diagnostic when there + {/* A turn's terminal marker states the OUTCOME of the turn, so + it belongs after the work it is the outcome of. The aborted + marker and the failed banner both used to render above the + timeline, where they read as a header on reasoning and tool + calls that had in fact all succeeded. */} + {ownsTurnChrome && turn.status === 'aborted' && ( + + + )} + {/* `description` carries the parked-resume diagnostic when there is one — it explains why the button did nothing, which outranks execution state on the one turn that can have both. */} {ownsTurnChrome && turn.status === 'failed' && props.failedReasonLabel && ( diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css index 3bfe551dfd..592dca5f70 100644 --- a/packages/ui/src/styles.css +++ b/packages/ui/src/styles.css @@ -764,15 +764,13 @@ .maka-turn-aborted-marker { gap: var(--space-1); - margin-block: var(--space-0-5) var(--space-1); + margin-block: var(--space-1) var(--space-0-5); padding: var(--space-0-5) var(--space-1-5); background: var(--foreground-5); color: var(--foreground-secondary); font-style: italic; } -.maka-turn-aborted-marker em { font-style: italic; } - .maka-turn-host-origin { align-self: flex-end; gap: var(--space-1); From 6456a63f379c7652b79edf6ff05933d71d44c05b Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sat, 29 Aug 2026 01:22:49 +0800 Subject: [PATCH 2/4] fix(ui): use Astryx badge for aborted turns Aborted turns are settled facts, but their outcome was rendered through a Maka-only Marker variant and bespoke visual recipe. Render the terminal outcome with Astryx's neutral Badge while preserving its position after the timeline, retain only the flex alignment needed by the transcript container, and cover both the component contract and ordering. Generated-by: Codex --- .../chat-turn-answer-identity.test.tsx | 28 +++++++++++++++++++ packages/ui/src/chat-turn.tsx | 11 +++++--- packages/ui/src/primitives/chat.tsx | 8 ++---- packages/ui/src/styles.css | 10 +------ 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx b/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx index ce6052f940..3189b5bc15 100644 --- a/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx +++ b/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx @@ -106,6 +106,34 @@ const RUNNING_TOOL: TurnTimelineItem = { items: [{ toolUseId: 'tool-1', toolName: 'read', status: 'running', args: {} }], }; +test('renders an aborted turn outcome with the neutral status badge', async () => { + const { container, root } = domRoot(); + await renderTurn(root, { + ...turnWith([{ ...ANSWER, live: false }]), + status: 'aborted', + abortSource: 'renderer.stop_button', + }); + + const outcome = container.querySelector('.astryx-badge[role="status"]'); + assert.ok(outcome, 'the aborted outcome remains announced as a status Badge'); + assert.equal(outcome.getAttribute('data-variant'), 'neutral'); +}); + +test('places the aborted turn outcome after its timeline content', async () => { + const { container, root } = domRoot(); + await renderTurn(root, { + ...turnWith([{ ...ANSWER, live: false }]), + status: 'aborted', + }); + + const content = container.querySelector('.maka-assistant-answer-content'); + const answer = container.querySelector('.maka-chat-message-bubble-assistant'); + const outcome = container.querySelector('.astryx-badge[role="status"]'); + assert.ok(content && answer && outcome); + const children = [...content.children]; + assert.ok(children.indexOf(outcome) > children.indexOf(answer)); +}); + /** * Keying the answer by its first timeline entry made the key change whenever * that entry did, so React unmounted the answer and mounted a copy — taking diff --git a/packages/ui/src/chat-turn.tsx b/packages/ui/src/chat-turn.tsx index c9e41c01a7..3a49ecc440 100644 --- a/packages/ui/src/chat-turn.tsx +++ b/packages/ui/src/chat-turn.tsx @@ -699,10 +699,13 @@ export const TurnView = memo(function TurnView(props: { timeline, where they read as a header on reasoning and tool calls that had in fact all succeeded. */} {ownsTurnChrome && turn.status === 'aborted' && ( - - +