diff --git a/desktop/src/components/activity/SessionActivityPanel.test.tsx b/desktop/src/components/activity/SessionActivityPanel.test.tsx index 387b318d..83cb2eed 100644 --- a/desktop/src/components/activity/SessionActivityPanel.test.tsx +++ b/desktop/src/components/activity/SessionActivityPanel.test.tsx @@ -101,10 +101,15 @@ function model(overrides: Partial = {}): SessionActivityMo describe('SessionActivityPanel', () => { afterEach(cleanup) - it('renders sections, rows, and empty states', () => { + it('renders populated tasks section without empty visible section labels', () => { render( { expect(screen.getByText('Add panel coverage')).toHaveAttribute('title', 'Add panel coverage') expect(screen.getByLabelText('Task in progress')).toBeInTheDocument() expect(screen.queryByText('In progress')).not.toBeInTheDocument() - expect(screen.getByText('Team')).toBeInTheDocument() - expect(screen.getByText('No team members')).toBeInTheDocument() - expect(screen.getByText('Background Tasks')).toBeInTheDocument() - expect(screen.getByText('SubAgents')).toBeInTheDocument() - expect(screen.getByText('Kuhn')).toBeInTheDocument() - expect(screen.getByText('Running')).toBeInTheDocument() - expect(screen.getByText('Sources')).toBeInTheDocument() - expect(screen.getByText('No background tasks')).toBeInTheDocument() - expect(screen.getByText('No sources')).toBeInTheDocument() + expect(screen.queryByText('Team')).not.toBeInTheDocument() + expect(screen.queryByText('Background Tasks')).not.toBeInTheDocument() + expect(screen.queryByText('SubAgents')).not.toBeInTheDocument() + expect(screen.queryByText('Sources')).not.toBeInTheDocument() + expect(screen.queryByText('No team members')).not.toBeInTheDocument() + expect(screen.queryByText('No background tasks')).not.toBeInTheDocument() + expect(screen.queryByText('No SubAgents')).not.toBeInTheDocument() + expect(screen.queryByText('No sources')).not.toBeInTheDocument() }) it('renders task rows as checklist markers instead of status chips', () => { @@ -386,11 +390,12 @@ describe('SessionActivityPanel', () => { ) expect(screen.getByTestId('session-activity-panel')).toHaveAttribute('data-placement', 'rail') - expect(screen.getByTestId('session-activity-panel')).toHaveClass('my-3') - expect(screen.getByTestId('session-activity-panel')).toHaveClass('mr-3') - expect(screen.getByTestId('session-activity-panel')).toHaveClass('rounded-xl') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('my-4') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('mr-4') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('w-[340px]') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('rounded-[24px]') expect(screen.getByTestId('session-activity-panel')).toHaveClass('self-start') - expect(screen.getByTestId('session-activity-panel')).toHaveClass('max-h-[min(480px,calc(100vh-88px))]') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('max-h-[min(480px,calc(100vh-96px))]') expect(screen.getByTestId('session-activity-panel')).not.toHaveClass('h-[calc(100%-24px)]') fireEvent.pointerDown(screen.getByRole('button', { name: 'Outside' })) @@ -455,10 +460,10 @@ describe('SessionActivityPanel', () => { const backgroundSection = document.querySelector('section[aria-label="Background Tasks"]') const subagentsSection = document.querySelector('section[aria-label="SubAgents"]') - expect(tasksSection?.querySelector('.max-h-40.overflow-y-auto')).toBeInTheDocument() - expect(teamSection?.querySelector('.max-h-32.overflow-y-auto')).toBeInTheDocument() - expect(backgroundSection?.querySelector('.max-h-36.overflow-y-auto')).toBeInTheDocument() - expect(subagentsSection?.querySelector('.max-h-36.overflow-y-auto')).toBeInTheDocument() + expect(tasksSection?.querySelector('.max-h-44.overflow-y-auto')).toBeInTheDocument() + expect(teamSection?.querySelector('.max-h-36.overflow-y-auto')).toBeInTheDocument() + expect(backgroundSection?.querySelector('.max-h-40.overflow-y-auto')).toBeInTheDocument() + expect(subagentsSection?.querySelector('.max-h-40.overflow-y-auto')).toBeInTheDocument() }) it('opens a SubAgent row when the row is openable', () => { diff --git a/desktop/src/components/activity/SessionActivityPanel.tsx b/desktop/src/components/activity/SessionActivityPanel.tsx index 48686787..4e761c02 100644 --- a/desktop/src/components/activity/SessionActivityPanel.tsx +++ b/desktop/src/components/activity/SessionActivityPanel.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef, useState } from 'react' -import { Check, LoaderCircle, X } from 'lucide-react' -import type { ActivityRow, ActivitySectionId, SessionActivityModel } from './sessionActivityModel' +import { Bot, Check, ChevronRight, Circle, FileText, LoaderCircle, Terminal, Users, X } from 'lucide-react' +import { getVisibleActivitySections, type ActivityRow, type ActivitySectionId, type SessionActivityModel } from './sessionActivityModel' import { useTranslation } from '../../i18n' import type { BackgroundAgentTask } from '../../types/chat' import type { TeamMember } from '../../types/team' @@ -14,8 +14,6 @@ export type OpenSubagentPayload = { type SessionActivityPanelPlacement = 'overlay' | 'rail' -const SECTION_ORDER = ['tasks', 'team', 'backgroundTasks', 'subagents', 'sources'] as const - type TranslationFn = ReturnType function fallbackStatusLabel(status: ActivityRow['status']): string { @@ -64,40 +62,23 @@ function getSectionTitle(sectionId: ActivitySectionId, t: TranslationFn): string } } -function getSectionEmptyLabel(sectionId: ActivitySectionId, t: TranslationFn): string { - switch (sectionId) { - case 'tasks': - return t('session.activity.empty.tasks') - case 'team': - return t('session.activity.empty.team') - case 'backgroundTasks': - return t('session.activity.empty.backgroundTasks') - case 'subagents': - return t('session.activity.empty.subagents') - case 'sources': - return t('session.activity.empty.sources') - case 'output': - return '' - } -} - function getSectionRowsClassName(sectionId: ActivitySectionId, rowCount: number): string { - const base = 'space-y-0.5' + const base = 'space-y-1' if (rowCount === 0) return base switch (sectionId) { case 'tasks': - return `${base} max-h-40 overflow-y-auto overscroll-contain pr-1` + return `${base} max-h-44 overflow-y-auto overscroll-contain pr-1` case 'team': - return `${base} max-h-32 overflow-y-auto overscroll-contain pr-1` + return `${base} max-h-36 overflow-y-auto overscroll-contain pr-1` case 'backgroundTasks': - return `${base} max-h-36 overflow-y-auto overscroll-contain pr-1` + return `${base} max-h-40 overflow-y-auto overscroll-contain pr-1` case 'subagents': - return `${base} max-h-36 overflow-y-auto overscroll-contain pr-1` + return `${base} max-h-40 overflow-y-auto overscroll-contain pr-1` case 'sources': - return `${base} max-h-24 overflow-y-auto overscroll-contain pr-1` + return `${base} max-h-28 overflow-y-auto overscroll-contain pr-1` case 'output': - return `${base} max-h-24 overflow-y-auto overscroll-contain pr-1` + return `${base} max-h-28 overflow-y-auto overscroll-contain pr-1` } } @@ -156,9 +137,9 @@ function TaskStatusMarker({ status, t }: { status: ActivityRow['status']; t: Tra return ( - ) } @@ -167,9 +148,9 @@ function TaskStatusMarker({ status, t }: { status: ActivityRow['status']; t: Tra return ( - ) } @@ -177,11 +158,40 @@ function TaskStatusMarker({ status, t }: { status: ActivityRow['status']; t: Tra return ( ) } +function getRowIcon(row: ActivityRow) { + switch (row.section) { + case 'team': + return Users + case 'backgroundTasks': + return Terminal + case 'subagents': + return Bot + case 'sources': + case 'output': + return FileText + case 'tasks': + return Circle + } +} + +function getStatusTone(status: ActivityRow['status']) { + if (status === 'running' || status === 'in_progress') { + return 'bg-[var(--color-accent)]' + } + if (status === 'completed' || status === 'idle') { + return 'bg-[var(--color-success)]' + } + if (status === 'failed' || status === 'error' || status === 'stopped') { + return 'bg-[var(--color-error)]' + } + return 'bg-[var(--color-text-tertiary)]' +} + function ActivityRowView({ row, sessionId, @@ -215,17 +225,26 @@ function ActivityRowView({ : undefined const content = ( <> - {isTask ? : null} + {isTask ? ( + + ) : ( + + {(() => { + const Icon = getRowIcon(row) + return + )} {label} {detail ? ( {detail} @@ -233,12 +252,18 @@ function ActivityRowView({ ) : null} {isTask ? null : ( - + + )} + {!isTask && row.openable ? ( +