From bfecc094c84d7785bb06f6dcc4c8c86d75df0fbb Mon Sep 17 00:00:00 2001 From: wenshao Date: Thu, 7 May 2026 18:20:20 +0800 Subject: [PATCH] fix(cli): widen LiveAgentPanel, drop [in turn] marker, point overflow at dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three usability fixes from review: 1. Use `terminalWidth` instead of `mainAreaWidth`. The latter is capped at 100 cols (intended for markdown / code where soft-wrap matters), which on a 200-col terminal left half the screen empty to the right of an already-truncating row. Live progress lines have nothing to soft-wrap, so the panel wants the full width. 2. Drop the `[in turn]` foreground marker. The flavor distinction matters in BackgroundTasksDialog (cancel semantics differ for foreground vs background entries) but in the glance panel the marker reads as cryptic noise — users asked what it meant. Keep the dialog as the surface that surfaces it. 3. Annotate the overflow callout with `(↓ to view all)`. The panel is intentionally read-only (it has no keyboard focus so it can't steal input from the composer), so when the roster outgrows the row budget we point users at the existing dialog — same keystroke the footer pill uses, kept in sync so users only learn one gesture. --- .../background-view/LiveAgentPanel.test.tsx | 15 +++++++++++--- .../background-view/LiveAgentPanel.tsx | 20 ++++++++++++++++--- .../cli/src/ui/layouts/DefaultAppLayout.tsx | 9 ++++++++- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/ui/components/background-view/LiveAgentPanel.test.tsx b/packages/cli/src/ui/components/background-view/LiveAgentPanel.test.tsx index 5402f50bf..8f89f874a 100644 --- a/packages/cli/src/ui/components/background-view/LiveAgentPanel.test.tsx +++ b/packages/cli/src/ui/components/background-view/LiveAgentPanel.test.tsx @@ -156,7 +156,10 @@ describe('', () => { expect(frame).toContain('5s'); }); - it('marks foreground agents with the [in turn] prefix', () => { + it('does NOT surface a flavor marker on foreground agents', () => { + // Foreground vs background distinction stays with BackgroundTasksDialog + // (where cancel semantics differ); the panel reads as a glance roster + // and the marker added more confusion than signal. const { lastFrame } = renderPanel({ entries: [ agentEntry({ @@ -167,7 +170,10 @@ describe('', () => { }), ], }); - expect(lastFrame() ?? '').toContain('[in turn]'); + const frame = lastFrame() ?? ''; + expect(frame).not.toContain('[in turn]'); + expect(frame).toContain('editor'); + expect(frame).toContain('tighten import order'); }); it('windows from the tail when entries exceed maxRows', () => { @@ -190,8 +196,11 @@ describe('', () => { ]; const { lastFrame } = renderPanel({ entries, maxRows: 2 }); const frame = lastFrame() ?? ''; - // `more above` callout flagged with the dropped count. + // `more above` callout flagged with the dropped count and points + // at the dialog (the only surface where the user can scroll + // through the full roster + take action). expect(frame).toContain('1 more above'); + expect(frame).toContain('to view all'); // Tail window keeps the newest two rows. expect(frame).toContain('mid-agent'); expect(frame).toContain('fresh-agent'); diff --git a/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx b/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx index 691f4c40a..47601b884 100644 --- a/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx +++ b/packages/cli/src/ui/components/background-view/LiveAgentPanel.tsx @@ -237,9 +237,17 @@ export const LiveAgentPanel: React.FC = ({ {overflow > 0 && ( + {/* + The panel is read-only (no keyboard focus — that would + steal input from the composer), so when the roster + overflows the row budget we point users at the dialog + that DOES support selection / scroll / cancel / resume. + Same keystroke the footer pill uses, kept in sync so + users only have to learn one thing. + */} {` ^ ${overflow} more above`} + >{` ^ ${overflow} more above (↓ to view all)`} )} {visible.map((entry) => ( @@ -255,7 +263,13 @@ const AgentRow: React.FC<{ entry: AgentDialogEntry; now: number }> = ({ }) => { const { glyph, color } = statusIcon(entry); const label = descriptionWithoutPrefix(entry); - const flavorPrefix = entry.flavor === 'foreground' ? '[in turn] ' : ''; + // Note: foreground vs background is intentionally not surfaced here. + // Earlier iterations prefixed foreground rows with `[in turn]` (the + // BackgroundTasksDialog convention), but in the panel context the + // marker reads as cryptic — the foreground / background distinction + // matters in the dialog (where cancel semantics differ) but the + // glance roster just needs identity + intent + cost. Keep the + // dialog as the place that surfaces the flavor distinction. const activity = activityLabel(entry); const elapsed = elapsedLabel(entry, now); const showType = @@ -296,7 +310,7 @@ const AgentRow: React.FC<{ entry: AgentDialogEntry; now: number }> = ({ {': '} )} - {`${flavorPrefix}${label}`} + {label} {activity && ( {` (${activity})`} )} diff --git a/packages/cli/src/ui/layouts/DefaultAppLayout.tsx b/packages/cli/src/ui/layouts/DefaultAppLayout.tsx index 124ceef0a..aa59ee12b 100644 --- a/packages/cli/src/ui/layouts/DefaultAppLayout.tsx +++ b/packages/cli/src/ui/layouts/DefaultAppLayout.tsx @@ -115,9 +115,16 @@ export const DefaultAppLayout: React.FC = () => { open (auth / permission / background tasks / etc.) so the modal surface doesn't compete with the live roster, and the panel's own internal self-hide handles the empty-roster case. + + Panel uses `terminalWidth`, not `mainAreaWidth` — `mainAreaWidth` + is hard-capped at 100 cols (intended for markdown / code blocks + where soft-wrap matters), which on wider terminals leaves a + large empty gutter to the right of an already-truncating row. + Live progress lines have nothing to soft-wrap, so the panel + wants the full terminal width. */} {!isAgentTab && !uiState.dialogsVisible && ( - + )} );