From bef9596ed5bf18965fb67999f084bed5573cf35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Wed, 22 Apr 2026 15:30:05 +0800 Subject: [PATCH] Keep all assistant-side transcript content on one shared content rail The desktop transcript had drifted into multiple competing left edges. Assistant bubbles, thinking rows, tool cards, permissions, and standalone results were using different offsets, which made the timeline feel visually broken even when the underlying data was correct. This change removes the ad hoc assistant-side indentation and makes the assistant output lane follow the same content rail as the composer. The assistant message component still distinguishes short bubble replies from markdown-heavy document replies, but both now sit on the same shared left alignment. Supporting chat blocks were updated to use that same rail so the whole transcript reads as one coherent column. Constraint: Assistant transcript content must align with the composer rail, not with local per-block offsets Rejected: Keep tool/thinking blocks on a separate inset lane | creates multiple left edges and keeps the transcript visually inconsistent Rejected: Fix only final assistant replies | leaves the rest of the assistant-side timeline misaligned Confidence: high Scope-risk: narrow Reversibility: clean Directive: Treat the composer rail as the canonical left edge for all assistant-side transcript blocks unless the entire transcript layout is redesigned together Tested: bun run test src/components/chat/MessageList.test.tsx --run; bun run lint; bun run build Not-tested: Real Tauri runtime screenshot against a live session after this unified alignment change --- .../src/components/chat/AskUserQuestion.tsx | 2 +- .../src/components/chat/AssistantMessage.tsx | 30 ++++++++++++++-- .../src/components/chat/MessageList.test.tsx | 36 +++++++++++++++++++ .../src/components/chat/PermissionDialog.tsx | 2 +- .../components/chat/StreamingIndicator.tsx | 2 +- desktop/src/components/chat/ThinkingBlock.tsx | 2 +- desktop/src/components/chat/ToolCallBlock.tsx | 2 +- desktop/src/components/chat/ToolCallGroup.tsx | 4 +-- .../src/components/chat/ToolResultBlock.tsx | 2 +- 9 files changed, 71 insertions(+), 11 deletions(-) diff --git a/desktop/src/components/chat/AskUserQuestion.tsx b/desktop/src/components/chat/AskUserQuestion.tsx index 67006926..93694544 100644 --- a/desktop/src/components/chat/AskUserQuestion.tsx +++ b/desktop/src/components/chat/AskUserQuestion.tsx @@ -135,7 +135,7 @@ export function AskUserQuestion({ toolUseId, input, result }: Props) { if (!activeQuestion) return null return ( -
-
- +
+ {!isStreaming && } {isStreaming && ( @@ -31,3 +40,18 @@ export function AssistantMessage({ content, isStreaming }: Props) {
) } + +function shouldUseDocumentLayout(content: string) { + const normalized = content.trim() + if (!normalized) return false + + if (/```/.test(normalized)) return true + if (/^\s{0,3}(#{1,6}\s|[-*+]\s|\d+\.\s|>\s|\|.+\|)/m.test(normalized)) return true + + const paragraphs = normalized + .split(/\n\s*\n/) + .map((chunk) => chunk.trim()) + .filter(Boolean) + + return paragraphs.length >= 2 || normalized.split('\n').filter((line) => line.trim()).length >= 8 +} diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx index 9e46d5c8..a0b77b1d 100644 --- a/desktop/src/components/chat/MessageList.test.tsx +++ b/desktop/src/components/chat/MessageList.test.tsx @@ -371,10 +371,46 @@ describe('MessageList nested tool calls', () => { expect(userShell?.className).toContain('items-end') expect(assistantShell).toBeTruthy() expect(assistantShell?.className).toContain('items-start') + expect(assistantShell?.className).not.toContain('ml-10') expect(userActions?.getAttribute('data-align')).toBe('end') expect(assistantActions?.getAttribute('data-align')).toBe('start') }) + it('uses the document column for markdown-heavy assistant replies', () => { + useChatStore.setState({ + sessions: { + [ACTIVE_TAB]: makeSessionState({ + messages: [ + { + id: 'assistant-doc', + type: 'assistant_text', + content: [ + '## 交付结果', + '', + '已完成以下内容:', + '', + '- 添加任务', + '- 删除任务', + '', + '```bash', + 'npm run build', + '```', + ].join('\n'), + timestamp: 1, + }, + ], + }), + }, + }) + + render() + + const assistantShell = screen.getByText('交付结果').closest('[data-message-shell="assistant"]') + expect(assistantShell?.getAttribute('data-layout')).toBe('document') + expect(assistantShell?.className).toContain('w-full') + expect(assistantShell?.className).not.toContain('ml-10') + }) + it('opens a rewind preview modal for user messages', async () => { vi.spyOn(sessionsApi, 'rewind').mockResolvedValue({ target: { diff --git a/desktop/src/components/chat/PermissionDialog.tsx b/desktop/src/components/chat/PermissionDialog.tsx index e1de3566..8e12ac17 100644 --- a/desktop/src/components/chat/PermissionDialog.tsx +++ b/desktop/src/components/chat/PermissionDialog.tsx @@ -127,7 +127,7 @@ export function PermissionDialog({ requestId, toolName, input, description }: Pr const allowRawToggle = !preview return ( -
+
{verb}... {elapsedSeconds > 0 && ( diff --git a/desktop/src/components/chat/ThinkingBlock.tsx b/desktop/src/components/chat/ThinkingBlock.tsx index dc97706e..3975525d 100644 --- a/desktop/src/components/chat/ThinkingBlock.tsx +++ b/desktop/src/components/chat/ThinkingBlock.tsx @@ -18,7 +18,7 @@ export function ThinkingBlock({ content, isActive = false }: { content: string; const preview = firstLine.length > 80 ? firstLine.slice(0, 80) + '...' : firstLine return ( -
+