You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(editor): make Settings modal and toolbar trailer truly global (CoreBunch#67)
The Settings modal is openable from every admin route, but its General
and Publishing sections read site data straight off the heavy editor
store, which is only hydrated on the Site editor. On Content / Data /
Media / Plugins / Users / Account those sections rendered a permanent
skeleton — the modal was "global" in name only. Separately, the settings
cog was injected per-layout: Site, Plugins/Users/Account, and Content
had it, but Media and Data did not.
Fix both:
- Hoist SettingsButton into the Toolbar shell so the trailer
(Settings → Open live page → Account) is identical on every route,
the same way the left nav is. Remove the per-layout/per-page
injections (AdminCanvasLayout, AdminPageLayout, ContentToolbar) and
the now-dead ToolbarDivider.
- Add useSiteSettingsController: one uniform site-settings source that
delegates to the editor draft on the Site editor (joins autosave) and
loads/saves via cmsAdapter (shell-only write + site-reload event)
everywhere else. General and Publishing consume it instead of the
editor store directly, so they work on every admin page without
forcing editor-store hydration into the light layouts.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/editor.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -574,6 +574,8 @@ The sidebar shell expands/collapses by animating `--*-panel-width`. The panel sl
574
574
-`ModulePickerDropdown` — opens the module inserter modal
575
575
-`OpenLivePageButton` (`src/admin/shared/OpenLivePageButton/`) — toolbar icon (always visible, not Site-editor-only) that opens the live site in a new tab. Target URL is read from `adminUi.activeLivePath`: active document's public path when an editor is open, site root (`/`) otherwise. Tooltip changes between "Open live page" (active path) and "Open live site" (null). Component stays outside `src/admin/pages/site/` so it mounts on every admin route without touching the editor graph.
576
576
577
+
**Global trailer.**`Toolbar.tsx` renders a fixed trailer at the right end of every admin route, regardless of which layout mounted it or what the caller passes in `rightSlot`: `SettingsButton` → `OpenLivePageButton` → `AccountMenuButton`. These are not layout- or page-owned — the settings cog, live-page link, and account menu are identical everywhere, the same way the left nav is. `SettingsButton` reads only the tiny `adminUi` store, so hosting it in the shell keeps the editor toolchain out of the lightweight admin bundles. Layouts use `rightSlot` only for surface-specific controls *before* the trailer (e.g. `ZoomControls` + `PublishButton` on the Site editor, the Uploads toggle on Media); pages must never inject their own `SettingsButton`.
578
+
577
579
### Settings modal
578
580
579
581
`src/admin/modals/Settings/SettingsModal.tsx`. Shares the visual language of the Spotlight palette and Module Inserter: a `--panel-*`-token shell, `--editor-surface-2` rail with categorical rail-tint icon chips, accent-bar section header, card-group rows (`--editor-surface-2` fills, `--panel-radius` corners, 1px gaps showing the darker panel surface through) for section content, and an Esc keycap affordance. Backdrop click and Esc both close — there is no dedicated close button.
@@ -591,6 +593,13 @@ Site-specific controls that were previously sections of this modal (Pages roster
591
593
592
594
**State bridge**: settings modal open/close state is mirrored between two stores. `adminUi` (`src/admin/state/adminUi.ts`) is the source the modal reads — this lets `SettingsButton` work on non-editor admin pages without pulling in the editor store. `settingsSlice` in the editor store mirrors that state via `bindSettingsBridgeStoreApi` so editor-side consumers (spotlight commands, tests) can open/navigate settings without knowing about `adminUi`. A re-entrance guard (`bridgeReentrancyGuard`) prevents the two-way sync from looping.
593
595
596
+
**Data source — `useSiteSettingsController`** (`src/admin/modals/Settings/useSiteSettingsController.ts`): the General and Publishing sections edit fields of the persisted `SiteDocument` (`name`, `settings.*`, framework preferences), but where that document lives depends on the route. The modal is global, so a section cannot just read the editor store — that store is only hydrated on the Site editor (`AdminCanvasLayout`). The controller hides the split behind one uniform shape:
597
+
598
+
-**Site editor** (editor store holds a live draft): delegate to the editor-store mutations. Settings edits join the unsaved draft and persist through the editor's autosave / Save pipeline alongside page-tree edits — never clobbered.
599
+
-**Every other admin page** (no in-memory draft): a standalone Zustand store loads the document once via `cmsAdapter`, edits a local copy, and persists immediately with a shell-only `saveSite` (empty dirty sets, so pages / components / layouts are left untouched). After each save it refreshes the `adminUi` site summary and fires `CMS_SITE_RELOAD_EVENT` so the toolbar brand and `useSiteSummary` re-sync. There is no Save button on those pages, so writes commit on blur / toggle.
600
+
601
+
Because the controller is imported only by the lazy section components, the editor-store import it carries stays inside the `SettingsModal` chunk and never enters the eager graph of the lightweight layouts. This is what makes the modal *actually* global — before it, General and Publishing rendered a permanent skeleton anywhere outside the Site editor.
602
+
594
603
`CanvasNotch` (`src/admin/pages/site/canvas/CanvasNotch.tsx`) owns the canvas-local insertion chrome. Its quick insert buttons are resolved from each admin's server-side `module-inserter` user preference; the default favorites are Container, Text, and Image. The full module inserter is the management surface for those favorites, so any insertable module, layout preset, or Visual Component can be pinned into the notch without adding a separate settings panel. In Visual Component mode, `CanvasRoot` mounts `VisualComponentModeControl` below the notch so the current component name, rename action, and page-return action stay attached to the canvas rather than the global toolbar. In live mode the notch accepts a `peek` prop — it parks above the top edge (clipped by `overflow:hidden`) and rolls down on hover/`:focus-within` so it does not overlay the page header; a slim `peekHandle` strip remains as the hover target.
595
604
596
605
### Module Inserter
@@ -704,6 +713,7 @@ See [docs/features/plugin-system.md](features/plugin-system.md) for the plugin S
704
713
-`src/admin/shared/OpenLivePageButton/OpenLivePageButton.tsx` — toolbar "Open live page" icon button
705
714
-`src/admin/pages/site/hooks/useActiveLivePath.ts` — resolves `activeLivePath` for the Site editor (including template → previewed page/post mapping)
706
715
-`src/admin/modals/Settings/SettingsModal.tsx` — settings modal (4 sections: General, Shortcuts, Publishing, Preferences)
716
+
-`src/admin/modals/Settings/useSiteSettingsController.ts` — uniform site-settings source for General/Publishing (editor draft on the Site editor, `cmsAdapter` everywhere else)
707
717
-`src/admin/pages/site/store/slices/settingsSlice.ts` — settings modal state + adminUi bridge
708
718
-`src/admin/state/useSiteSummary.ts` — lightweight site name/favicon fetch for non-editor layouts
0 commit comments