From b5aef05e2cd50bc6e4f5cda7ea18e1395c9938b3 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 15 Jul 2026 03:30:25 -0400 Subject: [PATCH] fix(desktop): layout reset reopens collapsed sidebars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resetLayoutTree() claims to "restore everything" but never reopened collapsed SIDES. A sidebar hidden before a reset survived it, so the next ⌘B toggled from that stale-hidden state into a SHOW — and the user's hide never persisted across reload. Reopen every bound side through its store so the toggles stay truthful. Adds a store-level regression test (real modules, re-import = reload) covering hide→reload and the reset→hide→reload repro. --- .../src/components/pane-shell/tree/store.ts | 8 +++ .../sidebar-collapse-persistence.test.ts | 64 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 apps/desktop/src/store/sidebar-collapse-persistence.test.ts diff --git a/apps/desktop/src/components/pane-shell/tree/store.ts b/apps/desktop/src/components/pane-shell/tree/store.ts index 9d120f6c1334..815c07a5f5ce 100644 --- a/apps/desktop/src/components/pane-shell/tree/store.ts +++ b/apps/desktop/src/components/pane-shell/tree/store.ts @@ -1148,6 +1148,14 @@ export function resetLayoutTree() { resetHandlers.forEach(fn => fn()) // Everything still missing (plugin panes) adopts by placement. adoptContributedPanes() + + // "Restore everything" includes collapsed SIDES: reopen every bound side + // (through its store, so $sidebarOpen / the toggles stay truthful). Without + // this a sidebar hidden before the reset silently survives it, flipping the + // next ⌘B into a SHOW — so hiding never appears to persist. + for (const side of Object.keys(sideOpeners) as TreeSide[]) { + sideOpeners[side]?.(true) + } } // Dev hook for automation. diff --git a/apps/desktop/src/store/sidebar-collapse-persistence.test.ts b/apps/desktop/src/store/sidebar-collapse-persistence.test.ts new file mode 100644 index 000000000000..f260950d1b95 --- /dev/null +++ b/apps/desktop/src/store/sidebar-collapse-persistence.test.ts @@ -0,0 +1,64 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +// Ground-truth repro for "hiding the sidebar doesn't persist on reload": drive +// the REAL stores, then re-import them (fresh module state reading persisted +// localStorage) to simulate a ⌃R reload. `bind` mirrors the controller wiring. +async function loadStores() { + const layout = await import('./layout') + const tree = await import('@/components/pane-shell/tree/store') + + return { + layout, + tree, + bind: () => tree.bindTreeSideVisibility('left', layout.$sidebarOpen, layout.setSidebarOpen), + leftCollapsed: () => tree.$collapsedTreeSides.get().has('left') + } +} + +const reload = () => vi.resetModules() // fresh modules; localStorage is the carry-over + +describe('sidebar collapse persistence', () => { + beforeEach(() => { + window.localStorage.clear() + vi.resetModules() + }) + + it('restores a hidden sidebar after a reload', async () => { + const s1 = await loadStores() + s1.bind() + s1.layout.setSidebarOpen(false) + expect(s1.leftCollapsed()).toBe(true) + + reload() + const s2 = await loadStores() + expect(s2.layout.$sidebarOpen.get()).toBe(false) // persisted open:false survives + s2.bind() + expect(s2.leftCollapsed()).toBe(true) // and re-collapses + }) + + // The reported repro: a sidebar HIDDEN before a reset must be reopened by the + // reset ("restore everything"); otherwise the stale-hidden state flips the + // next ⌘B into a SHOW, and the user's hide never persists. + it('reset reopens a hidden sidebar, so a later hide persists across reload', async () => { + const s1 = await loadStores() + const { group, split } = await import('@/components/pane-shell/tree/model') + s1.tree.declareDefaultTree(split('row', [group(['sessions']), group(['workspace'])], [1, 3])) + s1.bind() + + s1.layout.setSidebarOpen(false) // hidden BEFORE the reset + expect(s1.leftCollapsed()).toBe(true) + + s1.tree.resetLayoutTree() // ⌘⇧ reset — restores everything, sidebar shown again + expect(s1.layout.$sidebarOpen.get()).toBe(true) + expect(s1.leftCollapsed()).toBe(false) + + s1.layout.toggleSidebarOpen() // ⌘B now genuinely hides + expect(s1.layout.$sidebarOpen.get()).toBe(false) + + reload() + const s2 = await loadStores() + expect(s2.layout.$sidebarOpen.get()).toBe(false) + s2.bind() + expect(s2.leftCollapsed()).toBe(true) + }) +})