From bb875a09561190416bc05f77808c4a42f0eb642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20T=C3=B6rnberg?= Date: Sat, 7 Nov 2026 12:37:09 +0100 Subject: [PATCH] editor: friendlier panels - collapsible rail, remembered state, no more overlap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rail sections (Outline/Blocks/Nodes) collapse via their headers like Experiments already did, so long pages stay navigable. Rail open state and section toggles persist in localStorage across reloads (the QA preview flow reloads constantly). The assistant panel shifts left while the dev panel is open instead of stacking exactly underneath it, and the rail/dev/assistant widths cap at the viewport on small screens. Tests: rail-sections harness (collapse, reload persistence, shifted assistant); harness boots with a fresh profile unless keepStorage is passed; in-memory localStorage shim for the origin-less jsdom. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- src/controller.ts | 9 ++++- src/persist.ts | 21 +++++++++++ src/ui/Assistant.tsx | 3 +- src/ui/Dock.tsx | 2 +- src/ui/Rail.tsx | 51 +++++++++++++++++++------ src/ui/hooks.ts | 15 +++++++- src/ui/styles.css | 9 +++-- test/harness.ts | 5 ++- test/rail-sections.test.tsx | 74 +++++++++++++++++++++++++++++++++++++ test/setup.ts | 16 ++++++++ 10 files changed, 185 insertions(+), 20 deletions(-) create mode 100644 src/persist.ts create mode 100644 test/rail-sections.test.tsx diff --git a/src/controller.ts b/src/controller.ts index 9e384d7..be5c088 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -24,6 +24,7 @@ import type { DropTarget } from './page/dnd' import type { BlockEditorDef, BlockInfo, InlineSession } from './registry' import { createStore } from './store' import type { Store } from './store' +import { readPersist, writePersist } from './persist' export interface Block extends BlockInfo { depth: number @@ -123,7 +124,7 @@ export class Controller { canRedo: false, busy: 0, toast: null, - rail: false, + rail: readPersist('cms-onpage.rail', false), dev: false, assistant: false, inline: null, @@ -155,6 +156,12 @@ export class Controller { this.reloadImpl = opts.reload ?? (() => this.softReload()) } + /** Left panel open state, remembered across reloads. */ + setRail(open: boolean): void { + writePersist('cms-onpage.rail', open) + this.store.set({ rail: open }) + } + // --- lifecycle ---------------------------------------------------------- start(): void { diff --git a/src/persist.ts b/src/persist.ts new file mode 100644 index 0000000..12c17c1 --- /dev/null +++ b/src/persist.ts @@ -0,0 +1,21 @@ +// Remembering small UI preferences across reloads (rail open, section +// collapsed). Editors reload the page constantly to preview; losing the +// panel state every time is the papercut this removes. All access is +// guarded: private mode, sandboxed iframes and some embeds throw. +export function readPersist(key: string, fallback: T): T { + try { + const raw = localStorage.getItem(key) + if (raw === null) return fallback + return (typeof fallback === 'boolean' ? raw === 'true' : JSON.parse(raw)) as T + } catch { + return fallback + } +} + +export function writePersist(key: string, value: unknown): void { + try { + localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value)) + } catch { + // preferences are best-effort + } +} diff --git a/src/ui/Assistant.tsx b/src/ui/Assistant.tsx index 5fd2ca2..b6bb2e3 100644 --- a/src/ui/Assistant.tsx +++ b/src/ui/Assistant.tsx @@ -69,6 +69,7 @@ function ProposalCard({ p, status, onResolve }: { p: Proposal; status: ProposalS export function Assistant() { const ctrl = useCtrl() const open = useEditorState((s) => s.assistant) + const devOpen = useEditorState((s) => s.dev) const selected = useEditorState((s) => s.selected) useEditorState((s) => s.labelsVersion) const store = conversationsFor(ctrl) @@ -140,7 +141,7 @@ export function Assistant() { const starters = scope.rootId ? BLOCK_STARTERS : PAGE_STARTERS return ( -