From c305c5ce317b962246ffa04fcd54a26ef56ca0d2 Mon Sep 17 00:00:00 2001 From: SSU-WEI HUANG Date: Sun, 26 Jul 2026 15:02:52 +0800 Subject: [PATCH] fix(web): keep session sidebar stable after selection (#1173) --- .../SessionList.directory-action.test.tsx | 25 +++++++++++++++++++ web/src/components/SessionList.test.ts | 4 +-- web/src/components/SessionList.tsx | 11 ++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/web/src/components/SessionList.directory-action.test.tsx b/web/src/components/SessionList.directory-action.test.tsx index 962a909e..5a1fb8f8 100644 --- a/web/src/components/SessionList.directory-action.test.tsx +++ b/web/src/components/SessionList.directory-action.test.tsx @@ -321,4 +321,29 @@ describe('SessionList collapse behavior', () => { expect(getProjectPanel().getAttribute('data-open')).toBe('true') }) }) + + it('keeps the previous selected path open when selection moves', async () => { + const sessions = [ + makeSession({ + id: 'session-first', + updatedAt: 100, + metadata: { path: '/work/first', name: 'First task', flavor: 'codex' }, + }), + makeSession({ + id: 'session-second', + updatedAt: 90, + metadata: { path: '/work/second', name: 'Second task', flavor: 'codex' }, + }) + ] + const { rerender } = render(renderSessionList(sessions, 'session-first')) + const firstPanel = screen.getByTitle('/work/first').nextElementSibling + + expect(firstPanel?.getAttribute('data-open')).toBe('true') + + rerender(renderSessionList(sessions, 'session-second')) + + await waitFor(() => { + expect(firstPanel?.getAttribute('data-open')).toBe('true') + }) + }) }) diff --git a/web/src/components/SessionList.test.ts b/web/src/components/SessionList.test.ts index f3354ff8..dea40225 100644 --- a/web/src/components/SessionList.test.ts +++ b/web/src/components/SessionList.test.ts @@ -441,9 +441,9 @@ describe('expandSelectedSessionCollapseOverrides', () => { machineId: 'machine-1' }) - expect(result.has('machine-1::/work/hapi')).toBe(false) + expect(result.get('machine-1::/work/hapi')).toBe(false) expect(result.get('sessions::machine-1::/work/hapi')).toBe(true) - expect(result.has('machine::machine-1')).toBe(false) + expect(result.get('machine::machine-1')).toBe(false) }) it('leaves missing session preview override unset', () => { diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index 9c17d4bb..805dd8e3 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -283,15 +283,16 @@ export function expandSelectedSessionCollapseOverrides( const next = new Map(overrides) let changed = false - // Expand project group if collapsed. Project and machine keys use true = collapsed. - if (overrides.has(group.key) && overrides.get(group.key)) { - next.delete(group.key) + // Keep auto-expanded paths open after selection moves so content above the + // clicked row does not collapse and displace the sidebar viewport. + if (overrides.get(group.key) !== false) { + next.set(group.key, false) changed = true } const machineKey = `machine::${group.machineId ?? UNKNOWN_MACHINE_ID}` - if (overrides.has(machineKey) && overrides.get(machineKey)) { - next.delete(machineKey) + if (overrides.get(machineKey) !== false) { + next.set(machineKey, false) changed = true }