From b4a8e89fe3ddce27421098c5fb1f6f2b93ef11fc Mon Sep 17 00:00:00 2001 From: wusumac <736139669@qq.com> Date: Sun, 2 Aug 2026 22:37:00 +0800 Subject: [PATCH] fix(web): write VAPID key only after hub registration; make running-section toggle keyboard-accessible with correct filtered state --- web/src/components/NewSession/index.tsx | 19 ++++++++----- .../SessionList.directory-action.test.tsx | 27 +++++++++++++++++++ web/src/components/SessionList.tsx | 11 +++++++- web/src/hooks/usePushNotifications.ts | 6 ++++- 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/web/src/components/NewSession/index.tsx b/web/src/components/NewSession/index.tsx index 900d9148..855763e4 100644 --- a/web/src/components/NewSession/index.tsx +++ b/web/src/components/NewSession/index.tsx @@ -253,13 +253,18 @@ export function NewSession(props: { if (!props.api) { return } - props.api.getClaudeCustomModels().then((result) => { - if (!cancelled) { - setClaudeCustomModels(Array.isArray(result.models) ? result.models : []) - } - }).catch(() => { - // Custom models are optional — fall back to the built-in presets. - }) + try { + props.api.getClaudeCustomModels().then((result) => { + if (!cancelled) { + setClaudeCustomModels(Array.isArray(result.models) ? result.models : []) + } + }).catch(() => { + // Custom models are optional — fall back to the built-in presets. + }) + } catch { + // Partial api clients (tests, older hub versions) without the + // method must not break the New Session form. + } return () => { cancelled = true } diff --git a/web/src/components/SessionList.directory-action.test.tsx b/web/src/components/SessionList.directory-action.test.tsx index eada39b6..bcedec27 100644 --- a/web/src/components/SessionList.directory-action.test.tsx +++ b/web/src/components/SessionList.directory-action.test.tsx @@ -462,9 +462,11 @@ describe('SessionList collapse behavior', () => { const runningPanel = () => screen.getByTitle('In progress').nextElementSibling expect(runningPanel()?.getAttribute('data-open')).toBe('true') + expect(screen.getByTitle('In progress').getAttribute('aria-expanded')).toBe('true') fireEvent.click(screen.getByTitle('In progress')) expect(runningPanel()?.getAttribute('data-open')).toBeNull() + expect(screen.getByTitle('In progress').getAttribute('aria-expanded')).toBe('false') fireEvent.click(screen.getByRole('button', { name: 'Search sessions' })) fireEvent.change(screen.getByPlaceholderText('Search sessions…'), { @@ -472,6 +474,31 @@ describe('SessionList collapse behavior', () => { }) expect(runningPanel()?.getAttribute('data-open')).toBe('true') + // The section stays reported open while searching even though the + // underlying collapsed state is still set. + expect(screen.getByTitle('In progress').getAttribute('aria-expanded')).toBe('true') + }) + + it('toggles the running section with the keyboard', () => { + const sessions = [ + makeSession({ + id: 'session-running', + active: true, + thinking: true, + updatedAt: 100, + metadata: { path: '/work/hapi', name: 'Running task', flavor: 'codex' }, + }), + ] + render(renderSessionList(sessions)) + + const header = screen.getByRole('button', { name: /In progress/ }) + expect(header.getAttribute('aria-expanded')).toBe('true') + + fireEvent.keyDown(header, { key: 'Enter' }) + expect(header.getAttribute('aria-expanded')).toBe('false') + + fireEvent.keyDown(header, { key: ' ' }) + expect(header.getAttribute('aria-expanded')).toBe('true') }) it('keeps the previous selected path open when selection moves', async () => { diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index a659ff91..d637745d 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -1427,10 +1427,19 @@ export function SessionList(props: {
setRunningSectionCollapsed((value) => !value)} + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + setRunningSectionCollapsed((value) => !value) + } + }} title={t('sessions.runningSection')} > - +