* fix(ui): mount only the active session chat iframe * repro(ui): mount-all-persisted session-chat iframes (#2815) Adds a regression-style reproduction for issue #2815: ContextPanel renders one full-application iframe for every chat tab (inactive tabs are only hidden via the Tailwind 'hidden' class, never unmounted), so a reload restores all persisted session-chat tabs from the ui-store and mounts N embedded OpenChamber apps in one browser tab. The test reads the real ContextPanel.tsx render block, drives the real useUIStore with the issue's persisted scenario (11 tabs, 8 read-only session-chat tabs), and models the render block with the real buildEmbeddedSessionChatURL helper, showing 8 live src iframes (7 hidden but loaded). * test(ui): adapt issue 2815 reproduction for active chat * fix(ui): unmount session chat when panel closes --------- Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com>
2.9 KiB
2.9 KiB
Context Surfaces
Purpose
packages/ui/src/lib/surfaces owns the declarative registry of context panel
surfaces — the desktop workspaces switched by the vertical rail on the right
edge (components/layout/ContextPanelRail.tsx) and rendered by
components/layout/ContextPanel.tsx.
Model
- A surface maps 1:1 to a
ContextPanelModetab mode inuseUIStore. availability: 'always'surfaces are always present on the rail.availability: 'has-content'surfaces (preview, chat) are hidden from the rail until a tab of their mode exists, and stay visible for as long as one does — they must not disappear while in use.defaultWidthFractionis the panel width as a fraction of the content area, used until the user manually resizes that surface (manual widths are stored per mode inuseUIStore.contextPanelByDirectory[dir].widthByMode).- Rail order is user-reorderable and persisted globally in
useUIStore.contextRailOrder;sortContextSurfacesapplies it on top of the registry's default order and appends any missing surfaces. getVisibleContextRailSurfacesis the single visibility filter shared by the rail and the global surface-switch shortcut (switch_context_surfaceinlib/shortcuts.ts): it drops the plan surface unless plan mode is enabled, drops the walkthrough on VS Code and belowWALKTHROUGH_MIN_WIDTH, and hideshas-contentsurfaces until a tab of their mode exists. Both consumers use it so the digit shown on a rail badge always maps to the same surface the shortcut opens.
Adding a surface
- Add a
ContextPanelModevalue inuseUIStore(type union plus the sanitizer whitelist insanitizeContextPanelTabs). - Register a descriptor here (icon, label key, availability, width fraction).
- Render the mode in
ContextPanel.tsx(content dispatch, label, icon). - Add label/hint i18n keys to every locale dictionary.
No new header buttons: the rail and openContextSurface are the only entry
points for opening surfaces directly; deep links from chat/palette go through
the openContext* actions in useUIStore.
Invariants
- Opening a surface must never require a control outside the rail, the command palette, or an in-content link.
- Multi-instance and session-holding surfaces (file/editor, diff, browser,
terminal) are keep-alive panes in
ContextPanel.tsx. Switching these surfaces must not reset their state (open tabs, xterm session, scroll positions). Chat tab records stay open, but only the active chat iframe is mounted while the panel is open. A selected chat restores its state from the session stores. A closed panel mounts no chat iframe. Singleton surfaces (git, pr, notes, plan, context) and preview tabs remount on switch. These surfaces must restore their state from stores or snapshots. - Runtime scope: desktop/web
MainLayoutonly. VS Code and the dedicated mobile shell have their own layouts and do not consume this registry.