From 63a7c128ecb4ddf324ff5aa6481750483841a81d Mon Sep 17 00:00:00 2001 From: AstroHan Date: Mon, 21 Sep 2026 14:55:20 +0800 Subject: [PATCH 1/2] fix(ui): keep the process disclosure inside the reading measure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ::details-content grid animating the process disclosure had an implicit auto column track, which grows to the item's max-content: one unbreakable descendant (a long code line, an ellipsized command) widened the processing body past the reading measure while the details' paint containment clipped every sibling at the column edge — the uniform clipped-text failure seen in the release screenshot. Pin the column to the grid's inline size so wide content overflows inside its own block instead. The body also clipped the tool rows' intentional 4px inline overhang (the Astryx padding/negative-margin pair that lets hover surfaces reach the card edge). The sequence now supplies that space as its own padding, and the body keeps only the block-axis clip the disclosure wipe needs. Refresh the stale ProcessingBlock comment, which still described the scrollable capped card removed in #5518, and assert the contract in the disclosure story: no child of the body may exceed the details' own box. Generated-by: Devin Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- apps/desktop/stories/app-shell.stories.tsx | 14 ++++++++++++-- packages/ui/src/chat-turn.tsx | 14 ++++---------- packages/ui/src/styles.css | 17 +++++++++++++++-- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/apps/desktop/stories/app-shell.stories.tsx b/apps/desktop/stories/app-shell.stories.tsx index 91122169bb..de3e479a3b 100644 --- a/apps/desktop/stories/app-shell.stories.tsx +++ b/apps/desktop/stories/app-shell.stories.tsx @@ -3744,8 +3744,8 @@ const processDisclosureMessages: StoredMessage[] = [ { type: 'assistant', id: 'process-check', turnId: 'process-turn', ts: NOW - 184_000, text: '我先检查登录状态的存储和恢复逻辑。', thinking: { text: '检查初始化时机与会话恢复顺序。' }, modelId: 'claude-sonnet-4-5' }, { type: 'tool_call', id: 'process-edit', turnId: 'process-turn', ts: NOW - 160_000, toolName: 'Edit', activityKind: 'edit', stepId: 'process-fix', args: { path: 'src/auth-store.ts', old_string: 'const session = null;', new_string: 'const session = restoreSession();' } }, { type: 'tool_result', id: 'process-edit-result', turnId: 'process-turn', ts: NOW - 150_000, toolUseId: 'process-edit', isError: false, content: { kind: 'text', text: 'Updated src/auth-store.ts' } }, - { type: 'assistant', id: 'process-fix', turnId: 'process-turn', ts: NOW - 149_000, text: '恢复时机有问题,接下来补上初始化。', modelId: 'claude-sonnet-4-5' }, - { type: 'tool_call', id: 'process-test', turnId: 'process-turn', ts: NOW - 90_000, toolName: 'Bash', activityKind: 'command', stepId: 'process-verify', args: { command: 'npm test -- auth-store.test.ts' } }, + { type: 'assistant', id: 'process-fix', turnId: 'process-turn', ts: NOW - 149_000, text: '恢复时机有问题,接下来补上初始化。\n\n```text\nconst session = restoreSession(readPersistedSessionSnapshotFromStorageWithMigratedLegacyKeyFormat(storage));\n```', modelId: 'claude-sonnet-4-5' }, + { type: 'tool_call', id: 'process-test', turnId: 'process-turn', ts: NOW - 90_000, toolName: 'Bash', activityKind: 'command', stepId: 'process-verify', args: { command: 'npm test -- auth-store.test.ts --coverage --reporter=verbose && npm run lint -- src/auth-store.ts src/session/*.test.ts --max-warnings=0' } }, { type: 'tool_result', id: 'process-test-result', turnId: 'process-turn', ts: NOW - 5_000, toolUseId: 'process-test', isError: false, content: { kind: 'text', text: 'Tests passed: 4' } }, { type: 'assistant', id: 'process-verify', turnId: 'process-turn', ts: NOW - 4_000, text: '初始化已补齐,现在运行登录状态的回归测试。', modelId: 'claude-sonnet-4-5' }, { type: 'assistant', id: 'process-answer', turnId: 'process-turn', ts: NOW, text: '已修复登录状态恢复。\n\n刷新页面后会恢复已有会话;相关测试通过。', modelId: 'claude-sonnet-4-5' }, @@ -3927,6 +3927,16 @@ export const CompletedProcessExpanded: Story = { await waitFor(() => expect(process.getBoundingClientRect().height).toBeGreaterThanOrEqual(summary.getBoundingClientRect().height + process.querySelector('.maka-processing-body')!.clientHeight - 1)); const processBody = process.querySelector('.maka-processing-body')!; await expect(getComputedStyle(processBody).overflowY).toBe('clip'); + // One unbreakable child must not widen the body's box past the details' + // own: the disclosure grid's column track is pinned, so the code line + // scrolls inside its block instead of pushing every sibling to a + // clipped off-canvas width. + const processBox = process.getBoundingClientRect(); + await expect(processBody.getBoundingClientRect().width).toBeLessThanOrEqual(processBox.width + 1); + for (const child of processBody.children) { + await expect(child.getBoundingClientRect().width, child.className.toString()) + .toBeLessThanOrEqual(processBox.width + 1); + } await expect(summary).toHaveFocus(); await expect(await within(canvasElement).findByText('我先检查登录状态的存储和恢复逻辑。')).toBeVisible(); const answer = await within(canvasElement).findByText('已修复登录状态恢复。'); diff --git a/packages/ui/src/chat-turn.tsx b/packages/ui/src/chat-turn.tsx index eeb1dcd2a9..f7ce207834 100644 --- a/packages/ui/src/chat-turn.tsx +++ b/packages/ui/src/chat-turn.tsx @@ -1456,16 +1456,10 @@ const TurnTimelineEntry = memo(function TurnTimelineEntry(props: { /** * The turn's whole execution process (reasoning, intermediate commentary, tool - * activity) as ONE bounded, scrollable card: a titled header row and, when - * open, a body that grows with its content up to a cap and then scrolls. The - * container's border is the card's frame, so a collapsed box keeps its outline - * and shows only the title row. - * - * The body owns its own scroll, not the transcript: a turn with hundreds of - * steps scrolls here instead of becoming an unreadable wall the reader has to - * traverse. While the body overflows, its top and/or bottom edge fades the - * content out, so the clipped rows read as "more above/below" rather than - * abruptly cut off. + * activity) as one in-flow disclosure: a titled header row and, when open, the + * full body laid out in the transcript's own scroll path. The container's + * border is the frame, so a collapsed box keeps its outline and shows only the + * title row. */ export const ProcessingBlock = memo(function ProcessingBlock(props: { activityObserved?: boolean; diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css index ff581cc4a3..1e273f7789 100644 --- a/packages/ui/src/styles.css +++ b/packages/ui/src/styles.css @@ -304,7 +304,13 @@ Product CSS keeps only what is layout: it is a block-level note under the bubble, so it needs its own line and the gap above it. */ .maka-turn-truncation-badge { display: flex; width: fit-content; margin-top: 6px; cursor: help; } -.maka-processing-sequence { min-width: 0; } +.maka-processing-sequence { + min-width: 0; + /* Astryx tool rows overhang their text by 4px on each inline edge so hover + surfaces reach the card edge (their padding/negative-margin pair); this + padding is where that overhang lands inside the paint boundary. */ + padding-inline: var(--space-1); +} .maka-processing-summary { display: flex; align-items: center; @@ -327,7 +333,9 @@ min-height: 0; flex-direction: column; gap: 8px; - overflow: clip; + /* Block-axis clip for the disclosure wipe only; the rows' inline overhang + must reach the sequence's padding edge, so it cannot be clipped here. */ + overflow-y: clip; } .maka-processing-body > :first-child { margin-top: var(--space-2); } /* Match Astryx ChatToolCalls' grid disclosure motion. The native details @@ -336,6 +344,11 @@ @supports selector(::details-content) { .maka-processing-sequence::details-content { display: grid; + /* An implicit `auto` column track grows to the item's max-content, so one + unbreakable row would widen the whole body past the reading measure and + every row would clip at the sequence's paint boundary. Pin the column to + the grid's inline size; overflow stays each child's own problem. */ + grid-template-columns: minmax(0, 1fr); grid-template-rows: 0fr; transition: grid-template-rows var(--duration-medium) var(--ease-standard), content-visibility var(--duration-medium) allow-discrete; From ef76ad7ad5f3a2ec8c167b670d3e3017ba4e53d6 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Mon, 21 Sep 2026 14:55:33 +0800 Subject: [PATCH 2/2] chore(desktop): drop dead transcript column declarations Verified by live CSSOM ablation (declarations removed from the served rules, every joint's geometry byte-identical, including the prompt rail's sticky position under scroll): - .maka-chat-layout duplicated the upstream ChatLayout root's display/flex/ min-height/width; only overscroll-behavior is product-owned. - position:relative on .maka-chat-shell and .maka-chatContent has no consumer: the prompt rail anchors to its own sticky host, quote actions use a fixed layer, virtua items resolve against their spacer. - width:100% on .maka-chat-message, .maka-chat-message-list and .maka-transcript-turn is stretch/redundant in every context; opacity:1 on .maka-chat-message is a leftover from the streaming fade-in. - .agents-chat-view-root duplicated .agents-chat-panel byte for byte; merge the selectors. Generated-by: Devin Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- apps/desktop/src/renderer/reference-shell.css | 9 +-------- apps/desktop/src/renderer/styles/chat-header.css | 7 ------- apps/desktop/src/renderer/styles/chat-message.css | 9 --------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/apps/desktop/src/renderer/reference-shell.css b/apps/desktop/src/renderer/reference-shell.css index 8b8fff3849..0e277bd6e0 100644 --- a/apps/desktop/src/renderer/reference-shell.css +++ b/apps/desktop/src/renderer/reference-shell.css @@ -96,14 +96,7 @@ box-shadow: none; } -.agents-chat-panel { - min-width: 0; - min-height: 0; - display: flex; - flex: 1 1 auto; - flex-direction: column; -} - +.agents-chat-panel, .agents-chat-view-root { min-width: 0; min-height: 0; diff --git a/apps/desktop/src/renderer/styles/chat-header.css b/apps/desktop/src/renderer/styles/chat-header.css index 2a1402e19c..438d0fa3df 100644 --- a/apps/desktop/src/renderer/styles/chat-header.css +++ b/apps/desktop/src/renderer/styles/chat-header.css @@ -186,23 +186,16 @@ } .maka-chatContent { - position: relative; width: 100%; box-sizing: border-box; padding-inline: var(--maka-transcript-gutter); } .maka-chat-layout { - width: 100%; - min-height: 0; - flex: 1 1 auto; - display: flex; - flex-direction: column; overscroll-behavior: contain; } .maka-chat-shell { - position: relative; min-height: 0; display: flex; flex-direction: column; diff --git a/apps/desktop/src/renderer/styles/chat-message.css b/apps/desktop/src/renderer/styles/chat-message.css index 45132f41f2..d3576b6b95 100644 --- a/apps/desktop/src/renderer/styles/chat-message.css +++ b/apps/desktop/src/renderer/styles/chat-message.css @@ -33,14 +33,6 @@ /* Astryx owns sender-aware row alignment; Maka only declares the product reading geometry and the deliberately dense conversation type scale. */ -.maka-chat-message { - width: 100%; - opacity: 1; -} - -.maka-chat-message-list { - width: 100%; -} /* Native anchoring handles ordinary content growth. The scroll authority disables it during following and atomic range replacement, when it owns @@ -60,7 +52,6 @@ .maka-transcript-turn { display: flex; - width: 100%; flex-direction: column; gap: var(--spacing-4); /* Resident Turns contribute their real size before the first scroll. */