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: {