2026-07-27 23:07:42 +03:00
|
|
|
# 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 `ContextPanelMode` tab mode in `useUIStore`.
|
|
|
|
|
- `availability: 'always'` surfaces are always present on the rail.
|
2026-08-13 22:44:13 +03:00
|
|
|
`availability: 'has-content'` surfaces (chat) are hidden from the
|
2026-07-27 23:07:42 +03:00
|
|
|
rail until a tab of their mode exists, and stay visible for as long as one
|
|
|
|
|
does — they must not disappear while in use.
|
|
|
|
|
- `defaultWidthFraction` is the panel width as a fraction of the content area,
|
|
|
|
|
used until the user manually resizes that surface (manual widths are stored
|
|
|
|
|
per mode in `useUIStore.contextPanelByDirectory[dir].widthByMode`).
|
|
|
|
|
- Rail order is user-reorderable and persisted globally in
|
|
|
|
|
`useUIStore.contextRailOrder`; `sortContextSurfaces` applies it on top of the
|
|
|
|
|
registry's default order and appends any missing surfaces.
|
2026-08-04 20:39:45 +03:00
|
|
|
- `getVisibleContextRailSurfaces` is the single visibility filter shared by the
|
|
|
|
|
rail and the global surface-switch shortcut (`switch_context_surface` in
|
|
|
|
|
`lib/shortcuts.ts`): it drops the plan surface unless plan mode is enabled,
|
|
|
|
|
drops the walkthrough on VS Code and below `WALKTHROUGH_MIN_WIDTH`, and hides
|
|
|
|
|
`has-content` surfaces 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.
|
2026-07-27 23:07:42 +03:00
|
|
|
|
|
|
|
|
## Adding a surface
|
|
|
|
|
|
|
|
|
|
1. Add a `ContextPanelMode` value in `useUIStore` (type union plus the
|
|
|
|
|
sanitizer whitelist in `sanitizeContextPanelTabs`).
|
|
|
|
|
2. Register a descriptor here (icon, label key, availability, width fraction).
|
|
|
|
|
3. Render the mode in `ContextPanel.tsx` (content dispatch, label, icon).
|
|
|
|
|
4. 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.
|
2026-08-11 15:11:25 +02:00
|
|
|
- Multi-instance and session-holding surfaces (file/editor, diff, browser,
|
|
|
|
|
terminal) are keep-alive panes in `ContextPanel.tsx`. Switching these
|
2026-07-27 23:07:42 +03:00
|
|
|
surfaces must not reset their state (open tabs, xterm session, scroll
|
2026-08-11 15:11:25 +02:00
|
|
|
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.
|
2026-08-13 22:44:13 +03:00
|
|
|
Singleton surfaces (git, pr, notes, plan, context) remount on switch. These
|
|
|
|
|
surfaces must restore their state from stores or snapshots.
|
2026-07-27 23:07:42 +03:00
|
|
|
- Runtime scope: desktop/web `MainLayout` only. VS Code and the dedicated
|
|
|
|
|
mobile shell have their own layouts and do not consume this registry.
|