diff options
| author | Adam Malczewski <[email protected]> | 2026-05-28 08:41:33 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-28 08:41:33 +0900 |
| commit | 6662622e1c89fa1124b7342f136ab5f8d3c97972 (patch) | |
| tree | 71a35186476e14af0ccaa1c11be24953e518a08e /packaging/[email protected] | |
| parent | d2e2e67425e5106025ee8082a0768989b5de814f (diff) | |
| download | dispatch-6662622e1c89fa1124b7342f136ab5f8d3c97972.tar.gz dispatch-6662622e1c89fa1124b7342f136ab5f8d3c97972.zip | |
feat(frontend): persist sidebar panel layout across browser refreshes via localStorage
Carries the in-app sidebar layout (which views are open and in what
order) across page reloads. Closes the natural follow-up to the tab-
restore feature in d2e2e67: tabs survive, but until now the sidebar
panels (Chat Settings / Tasks / Skills / Tools / etc.) reset to a
single default panel on every load.
Scope (explicitly bounded by the user):
- Persistence target: localStorage. Matches the precedent for UI
preferences (`dispatch-theme`, `dispatch-api-url`). Per-device
layout; no backend round-trip.
- sidebarOpen (the Header button that hides the whole sidebar
column) is NOT persisted; always starts open on every load.
- No drag-to-reorder UI added — persistence captures whatever order
the user established via the existing add/remove buttons.
Implementation:
• New `packages/frontend/src/lib/sidebar-storage.ts` — pure
functions `loadSidebarPanels(): string[]` and
`saveSidebarPanels(selected: string[]): void`. localStorage key
is `dispatch-sidebar-panels` (canonical `dispatch-` prefix).
`loadSidebarPanels` is defensive against every failure mode
(missing key, malformed JSON, non-array root, non-string entries,
empty-after-filter, localStorage.getItem throwing under
SecurityError). Returns a fresh array on every call so mutations
by the caller don't pollute the module-level default constant.
`saveSidebarPanels` swallows storage errors (quota / disabled /
SecurityError) — best-effort.
• `packages/frontend/src/lib/components/SidebarPanel.svelte`:
seed the `panels: $state` from `loadSidebarPanels().map(s => ({
id: nextId++, selected: s }))` and add a `$effect` that calls
`saveSidebarPanels(panels.map(p => p.selected))` whenever
`panels` changes. The session-ephemeral `id` field is regenerated
on every mount; only the `selected` strings round-trip.
• Existing addPanel / remove / dropdown handlers untouched — they
all reassign `panels` (`panels = [...panels, ...]`,
`panels = panels.filter(...)`, `panels = panels.map(...)`),
which triggers the new $effect. The minimum-one-panel invariant
(X button hidden on idx 0) is preserved at the UI layer and
reinforced by the loader's empty-fallback to the default layout.
Tests: 15 new in `packages/frontend/tests/sidebar-storage.test.ts` —
load with empty / valid / malformed / non-array / null / mixed-type /
empty-after-filter / throwing-getItem; save round-trip; save error
swallowing; overwrite semantics; empty-save / load-fallback; mutation
isolation.
Frontend total: 59 tests (was 44; +15). API 31, core 168 unchanged.
Typecheck clean (svelte-check 0 errors), biome clean (126 files).
Gemini code review (yolo mode, prompt-level write restriction to
report.md only): SHIP, no findings.
Diffstat (limited to 'packaging/[email protected]')
0 files changed, 0 insertions, 0 deletions
