fix(gui): keep sidebar controls when collapsed

A closed sidebar previously resolved to a zero-width grid track, clipping
the only toggle and the settings entry with no visible recovery; the closed
preference persisted across reloads, locking the sidebar shut.

- columns.ts maps the closed preference (width 0) to a fixed 60px
  SIDEBAR_COLLAPSED rail through every step of the concession solve;
  closed details still resolve to zero width.
- AppFrame derives data-sidebar-collapsed and the sidebar slot's collapsed
  owner prop from the persisted preference instead of the resolved track
  width, and drops the resize handle while collapsed.
- SidebarRoot reads the owner collapsed prop; the expanded-only body is a
  separate component that unmounts while collapsed (dropping its sessions
  subscription), leaving the expand toggle and Settings in the rail.
- The keyless web smoke gains the ui-sidebar bundle (six real bundles) and
  pins the 60px rail collapse/expand round through the assembled client.
This commit is contained in:
imccyu
2026-07-23 10:51:57 +08:00
parent 59a2fc408b
commit 7e6f0128b5
12 changed files with 153 additions and 80 deletions

View File

@@ -60,17 +60,24 @@ function mount(...summaries: SessionSummary[]) {
const sessions = createSnapshotStore<SessionListState>(listStateOf(...summaries))
const onOpen = vi.fn((id: SessionId) => { sessions.update((d) => { d.current = id }) })
const onCreate = vi.fn()
const onToggleSidebar = vi.fn()
const utils = render(
// The owner decides collapsed in production (AppFrame maps the preference);
// the harness mirrors that loop so the toggle drives a re-render.
let collapsed = false
const view = (width: number) => (
<SidebarRoot
collapsed={false}
width={300}
collapsed={collapsed}
width={width}
useSessions={hookOf(sessions)}
onOpen={onOpen}
onCreate={onCreate}
onToggleSidebar={onToggleSidebar}
/>,
/>
)
const onToggleSidebar = vi.fn(() => {
collapsed = !collapsed
utils.rerender(view(collapsed ? 60 : 300))
})
const utils = render(view(300))
return { sessions, onOpen, onCreate, onToggleSidebar, ...utils }
}
@@ -151,10 +158,23 @@ describe('SidebarRoot', () => {
expect(onCreate).toHaveBeenLastCalledWith('/proj')
})
it('collapse button and group-by menu behave', () => {
it('collapsed rail keeps the expand and settings controls', () => {
const { onToggleSidebar } = mount(...projectData())
act(() => { fireEvent.click(screen.getByLabelText('Collapse sidebar')) })
expect(onToggleSidebar).toHaveBeenCalledOnce()
expect(screen.getByLabelText('Expand sidebar')).toBeTruthy()
expect(screen.getByLabelText('Settings')).toBeTruthy()
expect(screen.queryByText('HARNESS')).toBeNull()
expect(screen.queryByText('New Session')).toBeNull()
expect(screen.queryByRole('tree')).toBeNull()
act(() => { fireEvent.click(screen.getByLabelText('Expand sidebar')) })
expect(onToggleSidebar).toHaveBeenCalledTimes(2)
expect(screen.getByLabelText('Collapse sidebar')).toBeTruthy()
expect(screen.getByText('New Session')).toBeTruthy()
})
it('group-by menu behaves', () => {
mount(...projectData())
expect(screen.queryByText('Update')).toBeNull()
act(() => { fireEvent.click(screen.getByLabelText('Group by')) })
expect(screen.getByText('Update')).toBeTruthy()