From 70fb6c429e05d3acc7e4e107b41b83eb09a7bfa9 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 14:55:40 +0800 Subject: [PATCH] Keep desktop transcript and plugin controls readable after layout regressions Two desktop surfaces regressed in ways that made the app harder to read and operate. The plugin header compressed summary cards and action buttons too early for the available width, and the rewind/copied affordances in chat were sharing the same horizontal lane as message bubbles, which visually broke the expected "user on the right, assistant on the left" transcript structure. This commit keeps both fixes narrow: the plugin header now delays its split layout and lets summary cards and controls reflow responsively, while chat message actions live inside each message column so interaction affordances no longer distort the bubble alignment. Constraint: Desktop settings and transcript layouts must remain readable in narrower window widths Rejected: Keep message actions inline with the bubble row | action buttons keep stretching the message lane and blur role alignment Rejected: Hide rewind and copy entirely until a larger redesign | removes the control instead of fixing the regression Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep transcript actions inside the message column unless a future redesign also redefines bubble alignment rules Tested: bun run test src/__tests__/pluginsSettings.test.tsx --run; bun run test src/components/chat/MessageList.test.tsx --run; bun run lint; bun run build; agent-browser verification against local mock desktop session Not-tested: Real Tauri runtime screenshot on a live backend session after these layout changes --- .../src/components/chat/AssistantMessage.tsx | 18 +++++---- .../src/components/chat/MessageActionBar.tsx | 14 +++++-- .../src/components/chat/MessageList.test.tsx | 37 +++++++++++++++++++ desktop/src/components/chat/UserMessage.tsx | 26 +++++++------ desktop/src/components/plugins/PluginList.tsx | 32 +++++++++++----- 5 files changed, 96 insertions(+), 31 deletions(-) diff --git a/desktop/src/components/chat/AssistantMessage.tsx b/desktop/src/components/chat/AssistantMessage.tsx index 4c7f0d89..65ac2a74 100644 --- a/desktop/src/components/chat/AssistantMessage.tsx +++ b/desktop/src/components/chat/AssistantMessage.tsx @@ -9,8 +9,11 @@ type Props = { export function AssistantMessage({ content, isStreaming }: Props) { return ( -
-
+
+
{!isStreaming && } @@ -18,12 +21,13 @@ export function AssistantMessage({ content, isStreaming }: Props) { )}
-
- + +
) } diff --git a/desktop/src/components/chat/MessageActionBar.tsx b/desktop/src/components/chat/MessageActionBar.tsx index 00c941d8..34070cae 100644 --- a/desktop/src/components/chat/MessageActionBar.tsx +++ b/desktop/src/components/chat/MessageActionBar.tsx @@ -5,6 +5,7 @@ type Props = { copyLabel: string onRewind?: () => void rewindLabel?: string + align?: 'start' | 'end' } export function MessageActionBar({ @@ -12,6 +13,7 @@ export function MessageActionBar({ copyLabel, onRewind, rewindLabel = 'Rewind to here', + align = 'start', }: Props) { const hasCopy = Boolean(copyText?.trim()) const hasRewind = Boolean(onRewind) @@ -19,7 +21,13 @@ export function MessageActionBar({ if (!hasCopy && !hasRewind) return null return ( -
+
{hasRewind && (
diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx index 09e8866d..9e46d5c8 100644 --- a/desktop/src/components/chat/MessageList.test.tsx +++ b/desktop/src/components/chat/MessageList.test.tsx @@ -338,6 +338,43 @@ describe('MessageList nested tool calls', () => { ) }) + it('keeps user actions anchored to the right bubble and assistant actions to the left bubble', () => { + useChatStore.setState({ + sessions: { + [ACTIVE_TAB]: makeSessionState({ + messages: [ + { + id: 'user-1', + type: 'user_text', + content: '请把这条 prompt 放在右侧', + timestamp: 1, + }, + { + id: 'assistant-1', + type: 'assistant_text', + content: '这条回复应该停在左侧。', + timestamp: 2, + }, + ], + }), + }, + }) + + render() + + const userShell = screen.getByText('请把这条 prompt 放在右侧').closest('[data-message-shell="user"]') + const assistantShell = screen.getByText('这条回复应该停在左侧。').closest('[data-message-shell="assistant"]') + const userActions = screen.getByRole('button', { name: 'Copy prompt' }).closest('[data-message-actions]') + const assistantActions = screen.getByRole('button', { name: 'Copy reply' }).closest('[data-message-actions]') + + expect(userShell).toBeTruthy() + expect(userShell?.className).toContain('items-end') + expect(assistantShell).toBeTruthy() + expect(assistantShell?.className).toContain('items-start') + expect(userActions?.getAttribute('data-align')).toBe('end') + expect(assistantActions?.getAttribute('data-align')).toBe('start') + }) + it('opens a rewind preview modal for user messages', async () => { vi.spyOn(sessionsApi, 'rewind').mockResolvedValue({ target: { diff --git a/desktop/src/components/chat/UserMessage.tsx b/desktop/src/components/chat/UserMessage.tsx index 58b7f98e..729ed489 100644 --- a/desktop/src/components/chat/UserMessage.tsx +++ b/desktop/src/components/chat/UserMessage.tsx @@ -13,8 +13,11 @@ export function UserMessage({ content, attachments, onRewind, rewindLabel }: Pro const hasText = content.trim().length > 0 return ( -
-
+
+
{attachments && attachments.length > 0 && ( )} @@ -27,16 +30,17 @@ export function UserMessage({ content, attachments, onRewind, rewindLabel }: Pro {content}
)} -
- {hasText && ( - - )} + {hasText && ( + + )} +
) } diff --git a/desktop/src/components/plugins/PluginList.tsx b/desktop/src/components/plugins/PluginList.tsx index 273a44bd..7d8df76a 100644 --- a/desktop/src/components/plugins/PluginList.tsx +++ b/desktop/src/components/plugins/PluginList.tsx @@ -102,7 +102,7 @@ export function PluginList() { return (
-
+
{t('settings.plugins.browserEyebrow')} @@ -129,8 +129,8 @@ export function PluginList() { )}
-
-
+
+
-
- - @@ -316,12 +326,14 @@ function SummaryCard({ icon: string }) { return ( -
-
+
+
{icon} - {label} + + {label} +
-
+
{value}