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. */ 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;