fix(desktop): wrap the Agent Teams header in narrow panes

The header had a fixed 1180px minimum that scrolled sideways and let
flex squeeze button labels into vertical stacks. The controls now wrap
onto their own row, and buttons and timings never wrap.
This commit is contained in:
程序员阿江(Relakkes)
2026-10-08 21:54:38 +08:00
parent 9d25e6120c
commit c1ec601c00
2 changed files with 26 additions and 12 deletions
@@ -122,9 +122,19 @@ describe('AgentTeamsWorkbench', () => {
expect(screen.getByTestId('agent-teams-canvas-task-2').getAttribute('data-state')).toBe('completed') expect(screen.getByTestId('agent-teams-canvas-task-2').getAttribute('data-state')).toBe('completed')
expect(screen.getByTestId('agent-teams-canvas-task-3').getAttribute('data-state')).toBe('running') expect(screen.getByTestId('agent-teams-canvas-task-3').getAttribute('data-state')).toBe('running')
// A narrow pane wraps the controls onto their own row rather than forcing
// a fixed-width header that scrolls sideways and squeezes button labels.
const headerRow = screen.getByTestId('agent-teams-live-controls').parentElement!
expect(headerRow.className).toContain('flex-wrap')
expect(headerRow.className).not.toMatch(/\bmin-w-\[\d+px\]/)
expect(headerRow.parentElement!.className).not.toContain('overflow-x-auto')
fireEvent.click(screen.getByRole('button', { name: 'Review history' })) fireEvent.click(screen.getByRole('button', { name: 'Review history' }))
expect(screen.queryByTestId('agent-teams-live-controls')).toBeNull() expect(screen.queryByTestId('agent-teams-live-controls')).toBeNull()
expect(screen.getByTestId('agent-teams-replay-controls')).toBeTruthy() expect(screen.getByTestId('agent-teams-replay-controls')).toBeTruthy()
for (const name of [/^(Play|Pause|Replay)$/, 'Back to live']) {
expect(screen.getByRole('button', { name }).className).toMatch(/(?=.*\bshrink-0\b)(?=.*\bwhitespace-nowrap\b)/)
}
const timeline = screen.getByRole('slider', { name: 'Replay timeline · click to seek' }) as HTMLInputElement const timeline = screen.getByRole('slider', { name: 'Replay timeline · click to seek' }) as HTMLInputElement
expect(timeline.value).toBe('1000') expect(timeline.value).toBe('1000')
@@ -321,9 +321,12 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
aria-label={t('agentTeams.title')} aria-label={t('agentTeams.title')}
className="relative flex h-full min-h-0 w-full flex-col overflow-hidden bg-[var(--color-surface)] text-[var(--color-text-primary)]" className="relative flex h-full min-h-0 w-full flex-col overflow-hidden bg-[var(--color-surface)] text-[var(--color-text-primary)]"
> >
<header className="shrink-0 overflow-x-auto border-b border-[var(--color-border)] bg-[var(--color-surface-container-lowest)]"> {/* A narrow pane wraps the controls onto a row of their own instead of
<div className="flex min-w-[1180px] items-center gap-5 px-[18px] py-2.5"> scrolling the header sideways; the team block and counters stay on
<div className="min-w-[260px]"> the first row. Nothing in a control wraps, only hint text truncates. */}
<header className="shrink-0 border-b border-[var(--color-border)] bg-[var(--color-surface-container-lowest)]">
<div className="flex flex-wrap items-center gap-x-5 gap-y-2.5 px-[18px] py-2.5">
<div className="min-w-0 max-w-full">
<div className="text-[11px] font-semibold text-[var(--color-text-tertiary)]"> <div className="text-[11px] font-semibold text-[var(--color-text-tertiary)]">
Agent Teams · {t('agentTeams.sharedTaskList')} Agent Teams · {t('agentTeams.sharedTaskList')}
</div> </div>
@@ -340,7 +343,7 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
</div> </div>
</div> </div>
<div className="flex shrink-0 items-center gap-[18px] border-x border-[var(--color-border)] px-[18px]"> <div className="flex shrink-0 items-center gap-[18px] border-l border-[var(--color-border)] pl-[18px]">
<div className="min-w-[74px]"> <div className="min-w-[74px]">
<div className="text-[11px] font-semibold text-[var(--color-text-tertiary)]"> <div className="text-[11px] font-semibold text-[var(--color-text-tertiary)]">
{t('agentTeams.stats.phase')} {t('agentTeams.stats.phase')}
@@ -359,7 +362,7 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
</div> </div>
{followingLive ? ( {followingLive ? (
<div data-testid="agent-teams-live-controls" className="flex min-w-0 flex-1 items-center gap-3.5"> <div data-testid="agent-teams-live-controls" className="flex min-w-0 flex-[1_1_480px] items-center gap-3.5">
<div className="flex shrink-0 items-center gap-1.5 rounded-full bg-[var(--color-info-container)] px-2.5 py-1"> <div className="flex shrink-0 items-center gap-1.5 rounded-full bg-[var(--color-info-container)] px-2.5 py-1">
<span className="agent-teams-live-dot h-1.5 w-1.5 rounded-full bg-[var(--color-info)]" aria-hidden="true" /> <span className="agent-teams-live-dot h-1.5 w-1.5 rounded-full bg-[var(--color-info)]" aria-hidden="true" />
<span className="whitespace-nowrap text-xs font-medium text-[var(--color-on-info-container)]"> <span className="whitespace-nowrap text-xs font-medium text-[var(--color-on-info-container)]">
@@ -369,7 +372,7 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
<span className="min-w-0 flex-1 truncate text-xs text-[var(--color-text-secondary)]"> <span className="min-w-0 flex-1 truncate text-xs text-[var(--color-text-secondary)]">
{liveHint} {liveHint}
</span> </span>
<Button variant="secondary" size="sm" onClick={enterReplay}> <Button variant="secondary" size="sm" className="shrink-0 whitespace-nowrap" onClick={enterReplay}>
{t('agentTeams.reviewHistory')} {t('agentTeams.reviewHistory')}
</Button> </Button>
<div className="shrink-0 border-l border-[var(--color-border)] pl-3.5"> <div className="shrink-0 border-l border-[var(--color-border)] pl-3.5">
@@ -380,11 +383,11 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
</div> </div>
</div> </div>
) : ( ) : (
<div data-testid="agent-teams-replay-controls" className="flex min-w-0 flex-1 items-center gap-3.5"> <div data-testid="agent-teams-replay-controls" className="flex min-w-0 flex-[1_1_560px] flex-wrap items-center gap-x-3.5 gap-y-2">
<Button <Button
variant="primary" variant="primary"
size="base" size="base"
className="w-[86px]" className="w-[86px] shrink-0 whitespace-nowrap"
icon={playing ? <Pause size={13} aria-hidden="true" /> : <Play size={13} aria-hidden="true" />} icon={playing ? <Pause size={13} aria-hidden="true" /> : <Play size={13} aria-hidden="true" />}
onClick={togglePlayback} onClick={togglePlayback}
> >
@@ -419,10 +422,10 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
) )
})} })}
</div> </div>
<div className="min-w-[200px] flex-1"> <div className="min-w-[180px] flex-1">
<div className="mb-1 flex justify-between text-[11px] font-semibold text-[var(--color-text-tertiary)]"> <div className="mb-1 flex justify-between gap-2 text-[11px] font-semibold text-[var(--color-text-tertiary)]">
<span>{t('agentTeams.replay.timeline')}</span> <span className="min-w-0 truncate">{t('agentTeams.replay.timeline')}</span>
<span className="font-mono tabular-nums"> <span className="shrink-0 whitespace-nowrap font-mono tabular-nums">
{formatDuration(cursorTime - startTime)} / {formatDuration(totalElapsed)} {formatDuration(cursorTime - startTime)} / {formatDuration(totalElapsed)}
</span> </span>
</div> </div>
@@ -472,6 +475,7 @@ export function AgentTeamsWorkbench({ sessionId }: { sessionId: string }) {
<Button <Button
variant="secondary" variant="secondary"
size="sm" size="sm"
className="shrink-0 whitespace-nowrap"
icon={<Radio size={12} aria-hidden="true" />} icon={<Radio size={12} aria-hidden="true" />}
onClick={returnToLive} onClick={returnToLive}
> >