feat(gui): morph the sidebar collapse instead of swapping renders

The collapse read as a hard cut: only the track width animated while the
panel content swapped instantly and the rail icons bore no relation to the
expanded layout. Now the four control rows persist across the transition —
collapse toggle, new session, new workspace, search, the same top-down
order as their expanded rows — and morph their geometry (row heights,
paddings, margins, capsule borders) on the deepsuite curve, so each rail
icon is its expanded control converging onto the 56px axis. Wide-only
content (brand, labels, input, session tree) cross-fades over 200ms, stays
mounted while the collapse animates, and unmounts at the 300ms settle,
still dropping the sessions subscription. The search query moves up to the
root and survives the round trip; rail search focuses the surviving input
after expand instead of remount-autofocus.
This commit is contained in:
imccyu
2026-07-23 13:03:32 +08:00
parent 5da8e3b787
commit 698f46b0c9
8 changed files with 413 additions and 217 deletions

View File

@@ -147,8 +147,11 @@ describe('web boot chain success pass (keyless, six real bundles, ?fixture)', ()
await expect.poll(firstTrack, { timeout: 2000 }).toBe(px)
}
await page.getByRole('button', { name: 'Collapse sidebar' }).click()
// Mid-collapse the wide chrome is still mounted, fading — not swapped out.
expect(await page.locator('text=HARNESS').count()).toBe(1)
await settledTrack('56px')
for (const name of ['Expand sidebar', 'New session', 'Search sessions', 'New workspace', 'Settings']) {
await expect.poll(() => page.locator('text=HARNESS').count(), { timeout: 2000 }).toBe(0)
for (const name of ['Expand sidebar', 'New session', 'New workspace', 'Search sessions', 'Settings']) {
await expect(page.getByRole('button', { name }).isVisible(), name).resolves.toBe(true)
}
await page.getByRole('button', { name: 'Expand sidebar' }).click()