fix: simplify task center status selection and rendering

This commit is contained in:
mesalogo
2026-09-20 22:03:27 +08:00
parent fe093bc15f
commit 6739e8e902
5 changed files with 36 additions and 79 deletions
@@ -363,6 +363,7 @@ describe('RightAssistantSidebar resizing', () => {
}
const view = render(sidebarElement(props))
fireEvent.click(screen.getByRole('button', { name: '项目任务 2' }))
fireEvent.click(screen.getByRole('button', { name: '全部' }))
const stats = within(screen.getByRole('region', { name: '当前会话统计' }))
expect(stats.getByText('01:01:01')).toBeVisible()
expect(stats.getByText('24')).toBeVisible()
@@ -385,29 +386,22 @@ describe('RightAssistantSidebar resizing', () => {
for (const task of tasks) expect(screen.queryByText(task.title)).not.toBeInTheDocument()
})
it('keeps an applied filter visible when collapsed and clears it without opening the filter panel', () => {
it('shows the task status tabs when expanded and defaults to active tasks', () => {
renderSidebar({ tasks: [{
id: 'completed', projectId: currentProject.id, title: 'Completed task', instructions: '',
origin: 'schedule', status: 'completed', createdAt: '2026-09-17T00:00:00Z'
}] })
const toggle = screen.getByRole('button', { name: '筛选任务' })
expect(screen.getByText('Completed task')).toBeVisible()
fireEvent.click(toggle)
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
fireEvent.click(toggle)
expect(toggle).toHaveAttribute('aria-expanded', 'false')
expect(toggle).toHaveAttribute('data-active', 'true')
expect(screen.queryByRole('group', { name: '筛选任务' })).not.toBeInTheDocument()
expect(screen.getByText('筛选:进行中 · 0 项')).toBeVisible()
expect(screen.getByText('当前筛选条件下没有任务。')).toBeVisible()
fireEvent.click(screen.getByRole('button', { name: '清除筛选' }))
expect(toggle).toHaveAttribute('data-active', 'false')
expect(screen.queryByText('Completed task')).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: '筛选任务' })).not.toBeInTheDocument()
const filters = within(screen.getByRole('group', { name: '筛选任务' }))
expect(filters.getByRole('button', { name: '进行中' })).toHaveAttribute('aria-pressed', 'true')
expect(screen.queryByText('Completed task')).not.toBeInTheDocument()
fireEvent.click(filters.getByRole('button', { name: '已结束' }))
expect(screen.getByText('Completed task')).toBeVisible()
})
it('scrolls the focused task filter into view when keyboard navigation reaches either end', () => {
renderSidebar()
fireEvent.click(screen.getByRole('button', { name: '筛选任务' }))
const filters = within(screen.getByRole('group', { name: '筛选任务' }))
const first = filters.getByRole('button', { name: '全部' })
const last = filters.getByRole('button', { name: '已结束' })
@@ -658,7 +652,7 @@ describe('RightAssistantSidebar resizing', () => {
expect(list).toBeVisible()
fireEvent.click(toggle)
expect(list).not.toBeVisible()
fireEvent.click(screen.getByRole('button', { name: '筛选任务' }))
fireEvent.click(toggle)
expect(toggle).toHaveAttribute('aria-expanded', 'true')
expect(list).toBeVisible()
})
@@ -676,7 +670,7 @@ describe('RightAssistantSidebar resizing', () => {
expect(within(stats).getByText(`项目:${currentProject.name}`)).toBeVisible()
expect(screen.getByRole('button', { name: '项目任务 4' })).toHaveAttribute('aria-expanded', 'false')
expect(screen.getByText('2 运行中')).toBeVisible()
fireEvent.click(screen.getByRole('button', { name: '筛选任务' }))
fireEvent.click(screen.getByRole('button', { name: '项目任务 4' }))
fireEvent.click(within(screen.getByRole('group', { name: '筛选任务' })).getByRole('button', { name: '已结束' }))
expect(screen.getByText('2 运行中')).toBeVisible()
view.rerender(sidebarElement({ tab: 'tasks', tasks: [] }))
@@ -701,10 +695,7 @@ describe('RightAssistantSidebar resizing', () => {
)
fireEvent.click(newTaskButton)
expect(onCreateCustomTask).toHaveBeenCalledOnce()
expect(
screen.queryByRole('group', { name: '筛选任务' })
).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: '筛选任务' })).toHaveAttribute('aria-expanded', 'false')
expect(screen.getByRole('group', { name: '筛选任务' })).toBeVisible()
expect(screen.queryByLabelText('定时任务标题')).not.toBeInTheDocument()
expect(screen.queryByText('最近任务')).not.toBeInTheDocument()
})
@@ -736,7 +727,6 @@ describe('RightAssistantSidebar resizing', () => {
approvals: [approval], onOpenTask, onRespondApproval
})
fireEvent.click(screen.getByRole('button', { name: '筛选任务' }))
const filters = within(screen.getByRole('group', { name: '筛选任务' }))
expect(filters.getAllByRole('button').map((button) => button.textContent))
.toEqual(['全部', '进行中', '暂停', '已结束'])
@@ -782,7 +772,6 @@ describe('RightAssistantSidebar resizing', () => {
}
const onRespondApproval = vi.fn()
renderSidebar({ tasks: [task], approvals: [approval], onRespondApproval })
fireEvent.click(screen.getByRole('button', { name: '筛选任务' }))
const row = screen.getByText(task.title).closest('article')!
expect(within(row).getByLabelText('等待审批: Confirm write')).toBeVisible()
expect(screen.queryByRole('heading', { name: '等待审批' })).not.toBeInTheDocument()
+13 -23
View File
@@ -7,7 +7,6 @@ import {
Copy,
ExternalLink,
FileText,
Filter,
Maximize2,
Minimize2,
Plus,
@@ -48,7 +47,6 @@ import {
isBrowserViewportOccluded
} from './browser-viewport-occlusion'
import {
findTaskSchedule,
TaskScheduleActions
} from './TaskScheduleActions'
import type {
@@ -883,8 +881,7 @@ export function RightAssistantSidebar({
useState('')
const [taskFilter, setTaskFilter] = useState<
'all' | 'active' | 'paused' | 'finished'
>('all')
const [taskFiltersExpanded, setTaskFiltersExpanded] = useState(false)
>('active')
const [taskListExpanded, setTaskListExpanded] = useState(false)
const [actionErrorState, setActionErrorState] = useState({
scope: { instanceId: activeWorkbarInstanceId, open },
@@ -1027,6 +1024,14 @@ export function RightAssistantSidebar({
}),
[approvalsByTask, taskFilter, topLevelTasks]
)
const schedulesByTaskKey = useMemo(() => {
const byKey = new Map<string, AssistantSchedule>()
for (const schedule of schedules) {
byKey.set(schedule.id, schedule)
byKey.set(schedule.taskId, schedule)
}
return byKey
}, [schedules])
useEffect(() => {
// Context switches preserve the selected instance and its pinned binding.
@@ -1981,21 +1986,8 @@ export function RightAssistantSidebar({
>
<Plus aria-hidden="true" size={13} />
</button>
<button
className="secondary-button task-center__filter-toggle"
aria-label={t('sidebar.tasks.filters.ariaLabel')}
title={t('sidebar.tasks.filters.ariaLabel')}
aria-expanded={taskListExpanded && taskFiltersExpanded}
aria-controls={`task-filters-${instance.id}`}
data-active={taskFilter !== 'all'}
onClick={() => {
setTaskListExpanded(true)
setTaskFiltersExpanded((expanded) => !expanded)
}}
type="button"
><Filter aria-hidden="true" size={14} /></button>
</div>
<div className="task-center__filters" id={`task-filters-${instance.id}`} hidden={!taskListExpanded || !taskFiltersExpanded}
<div className="task-center__filters" id={`task-filters-${instance.id}`} hidden={!taskListExpanded}
onFocusCapture={(event) => event.target.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })}>
<SegmentedControl
ariaLabel={t('sidebar.tasks.filters.ariaLabel')}
@@ -2018,10 +2010,6 @@ export function RightAssistantSidebar({
value={taskFilter}
/>
</div>
{taskListExpanded && taskFilter !== 'all' && <div className="task-center__filter-summary">
<span>{t('sidebar.tasks.filters.summary', { filter: t(`sidebar.tasks.filters.${taskFilter}`), count: filteredTasks.length })}</span>
<button type="button" onClick={() => setTaskFilter('all')}>{t('sidebar.tasks.filters.clear')}</button>
</div>}
{unassociatedApprovals.length > 0 && <>
<h3><ShieldAlert size={15} />{t('sidebar.tasks.approvalsTitle')}</h3>
{unassociatedApprovals.map((approval) => (
@@ -2037,7 +2025,9 @@ export function RightAssistantSidebar({
</p>
) : (
filteredTasks.map((task) => {
const schedule = findTaskSchedule(task, schedules)
const schedule =
schedulesByTaskKey.get(task.scheduleId ?? '') ??
schedulesByTaskKey.get(task.id)
const conversationTitle = task.conversationId
? conversationTitles.get(task.conversationId)
: undefined
+7 -29
View File
@@ -81,8 +81,13 @@
.task-center__list { display: flex; flex-direction: column; gap: var(--space-2); }
.task-center__list[hidden] { display: none; }
.task-center .task-center__create,
.task-center .task-center__filter-toggle {
/* Keep expanding a long history responsive by skipping off-screen cards. */
.task-center__list > .task-center__item {
content-visibility: auto;
contain-intrinsic-size: 0 132px;
}
.task-center .task-center__create {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -92,13 +97,6 @@
padding: 0;
}
.task-center .task-center__filter-toggle[data-active='true'],
.task-center .task-center__filter-toggle[aria-expanded='true'] {
border-color: var(--accent-selected);
background: var(--accent-subtle);
color: var(--accent);
}
.task-center .task-center__filters { flex: 0 0 auto; }
.task-center .task-center__filters .segmented-control { overflow-x: auto; }
.task-center .task-center__filters .segmented-control__option {
@@ -107,26 +105,6 @@
}
.task-center .task-center__filters[hidden] { display: none; }
.task-center__filter-summary {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
color: var(--text-secondary);
font-size: var(--font-caption);
}
.task-center__filter-summary button {
border: 0;
background: transparent;
color: var(--accent);
padding: var(--space-2);
cursor: pointer;
font: inherit;
}
.task-center__filter-summary button:hover { background: var(--accent-subtle); }
.task-center button:focus-visible,
.task-center summary:focus-visible {
outline: 2px solid var(--accent);