Canonical vocabulary for Dormouse's states, entities, and transitions. Every other spec defers here when naming a state, a surface kind, or a verb; pick names from here first.
A Surface is the durable occupant of a Pane — the content in a slot. Three kinds:
- a terminal Surface, which Dormouse calls a Session: a PTY-backed shell with scrollback and semantic terminal state. The six-axis model below describes this kind.
- a tool Surface: a Session with terminal and browser capabilities (
docs/specs/dor-tool.md). - a browser Surface: a web view (
docs/specs/dor-browser.md), taking only a subset of the axes (Panes and Surfaces).
Unless a passage says "Surface" or "browser Surface," it describes a Session. A Session's state lives on six distinct axes; an operation can change several together. Their separate preconditions define the Liskov contract.
A Pane is one Lath leaf, a slot in the tiling layout (docs/specs/tiling-engine.md); lib/src/components/Wall.tsx owns Panes and Surfaces both.
A Pane holds one primary Surface today; helpers share its Pane (Containers). The model reserves several primary Surfaces per Pane (a future in-pane surface strip), so dor targets content by Surface id (surface:N), reserving Pane refs for layout-only commands (rationale).
Surface kinds — the kind a dor handle reports, derived from the Pane's params, never stored on the id:
| Kind | Sub-kinds | Backed by |
|---|---|---|
terminal |
— | a PTY + xterm.js instance — a Session |
tool |
— | a PTY and an optional browser on the same Session |
browser |
iframe, agent-browser-screencast, agent-browser-popout, playwright-screencast, playwright-popout |
an iframe proxy grant, or an automation-provider session (docs/specs/dor-browser.md) |
For a browser Surface renderMode is canonical; the CLI render_mode is derived from it and never stored.
| Surface | Persisted surfaceType (docs/specs/transport.md) |
renderMode (docs/specs/dor-browser.md) |
CLI kind |
CLI render_mode |
|---|---|---|---|---|
| tool Session | 'tool' |
Tool renderer when serving | tool |
renderer or null |
| terminal Session | 'terminal' (default, omitted) |
— | terminal |
null |
| browser Surface | 'browser' |
Browser renderer | browser |
same as renderMode |
Kinds are capability sets, not exclusive categories — terminal and browser carry one capability each, tool both. Operations gate on the capability they need, never on the kind enum (Liskov contract): read / send / await / port scans need the terminal, nav / render-mode / agent-browser verbs the browser. dor list --json rows always emit has_terminal and has_browser (rationale). Must declare each kind's capabilities in the hasTerminal / hasBrowser table. Persistence keeps its own PersistedSurfaceType discriminant (docs/specs/transport.md).
Source of truth: hasTerminal / hasBrowser in dor/src/commands/types.ts; surfaceKindFromParams in lib/src/components/wall/browser-surface.ts.
A Session runs all six axes (Layers); a browser Surface participates only where a web view meaningfully can:
| Axis | Browser Surface |
|---|---|
| View | full — all four states apply |
| Activity | TODO only — user-flagged; never reaches ALERT_RINGING (no BEL/OSC source) |
| Snapshot | surfaceType + render params, plus the alert blob's todo |
| Process | none — lifetime is the agent-browser session or proxy grant, keyed outside the Pane id |
| Registry | none — DOM is LathHost's leaf div (never re-parented); focus via a lightweight handle |
| Link | none — rebuilt from persisted params, not replayed |
The containment hierarchy dor handles commit to (docs/specs/dor-cli.md):
Window ⊃ Workspace ⊃ Pane ⊃ Surface (terminal = Session | browser)
Surface identity: minting: docs/specs/transport.md → Surface ids; the id surface:<n> is the dor handle: docs/specs/dor-cli.md → Handle Model. A primary Surface's id is its Lath leaf id, and a helper receives its Lath leaf only on promotion. A terminal Surface's id is its SessionId, stable (I1); a browser Surface's across render swaps and replacement: I10.
Must keep a helper as an auxiliary terminal Surface in its source's Pane, with a stable Session id and explicit parent association. It has no independent Lath leaf, targetable id, or alerting until promotion; docs/specs/terminal-context.md owns its lifetime. A shown helper is Paned within the source body; a closed context leaves it Hidden and DOM-parked (Mounted).
Workspace and Window are containers, not Session layers — they group Surfaces rather than describing one Surface's state (containment is I7).
| Container | Holds | Owner |
|---|---|---|
| Window | One or more Workspaces; the OS frame (a standalone Tauri window) or the host frame (a VS Code window). A host may hold several, and a Workspace may move between them. Its Tauri label is its persistence identity — one snapshot per label (docs/specs/standalone.md → Windows). |
host (Tauri / VS Code) |
| Workspace | "A window's worth of panes": a WorkspaceId, a user-facing name, its Panes and Surfaces, and the layout arranging them (Lath snapshot + doors). Exactly one Wall renders one Workspace. |
lib/src/lib/workspace-store.ts (the model), lib/src/components/Wall.tsx at render time; persisted per docs/specs/transport.md |
How many Workspaces a Window shows at once is host-specific: docs/specs/layout.md → "Conceptual model".
| Term | Meaning |
|---|---|
| Wall | The component rendering one Workspace: its Panes plus the Baseboard (lib/src/components/Wall.tsx) |
| Baseboard | The always-visible strip along the bottom of a Wall that holds its Doors |
| Door | A minimized Surface's tile on the Baseboard — the Doored View state |
docs/specs/layout.md owns their placement, sizing, and interaction.
A Workspace's union status is its display projection of member Surfaces' Activity; docs/specs/alert.md → Workspace union owns its fields and rules.
Remote control has exactly three roles. docs/specs/remote-security-model.md owns the trust between them; these are the names.
| Role | What it is | What it decides |
|---|---|---|
| Burrow | The app that owns terminal Surfaces and the processes behind them: the Standalone app, or the VS Code extension. Two on one machine are two Burrows, enrolled and paired separately, and Pocket lists them as two rows. | Every remote-access grant. Pairing approval and the ACL live here and nowhere else. |
| Client | What controls a Burrow from somewhere else. Pocket is the phone Client (docs/specs/pocket-app.md); the one-time page Hosted serves is a Client for one session (docs/specs/one-time.md); a VR headset is a future one (docs/specs/remote-api.md → Future). |
Nothing on its own — a Client asks. |
| Relay | The coordinating server: accounts, presence, push fan-out, and an encrypted byte pipe between Client and Burrow (docs/specs/relay.md). Dormouse Hosted is the managed Relay; SELF_HOST.md runs your own. |
Routing. It holds no terminal and no authorization. |
Hosted's one-time room is a rendezvous, not a fourth role: it forwards only a one-time connection's handshake frames, then closes (docs/specs/one-time.md -> "Hosted rendezvous").
A Burrow is a platform host — the process behind the webview that owns the PTYs — seen from the side a Client pairs with. Host stays the implementation word for that side (lib/src/host/, webview↔host messages, pty-host, VS Code's extension host) and keeps its Host-header, hostname, and self-host senses; never use Host for the remote-control role, or Server for the Relay.
A Wall is always in exactly one input mode; docs/specs/layout.md owns the switching gestures and per-mode behavior. Canonical names:
| Mode | Meaning |
|---|---|
| passthrough | Keyboard input routes to the selected Surface; docs/specs/layout.md → Modes owns interception. |
| command | Keyboard input drives navigation and layout commands; the Session receives nothing. |
Never introduce aliases — "terminal mode", "normal mode", and "navigation mode" all mean one of the two names above.
| Layer | Tracks | Owner |
|---|---|---|
| Process | PTY life on the host | vscode-ext/src/pty-manager.ts (VS Code) / standalone/sidecar/pty-core.js (standalone) |
| Registry | xterm.js Terminal + persistent DOM element | lib/src/lib/terminal-registry.ts facade over terminal-store.ts, terminal-lifecycle.ts |
| View | Where and how a Surface renders | lib/src/components/Wall.tsx plus lib/src/components/wall/ |
| Link | Webview ↔ host relationship | lib/src/lib/reconnect.ts |
| Activity | Alert / engagement state machine + renderer cache | lib/src/lib/alert-manager.ts, lib/src/lib/session-activity-store.ts |
| Snapshot | Persisted-to-disk projection: cwd, title, untouched, alert — never scrollback (docs/specs/transport.md) |
lib/src/lib/session-save.ts / session-restore.ts |
A Session is the tuple of its SessionId plus one state per layer (I1).
| State | Meaning |
|---|---|
Live |
Running, receiving and emitting data |
Exited |
Process ended; exit buffer retained for inspection |
Tombstoned |
User-killed; the host refuses to recreate a buffer even for a late data / exit (killedPtyIds in vscode-ext/src/pty-manager.ts). The standalone sidecar drops the record on kill, so Tombstoned and Absent are indistinguishable there |
Absent |
No host record at all |
| State | Meaning |
|---|---|
Unregistered |
No entry in terminal-registry |
Mounted |
Entry present, DOM element in the document tree |
Orphaned |
Entry present, element detached. Not transient — a Doored terminal Surface sits here as long as it stays minimized, and so does a Paned one in a hidden Workspace (I4) |
Disposed |
Entry removed, xterm disposed |
| State | Meaning |
|---|---|
Paned |
Rendered in the content area: a primary Lath leaf or its shown auxiliary helper |
Zoomed |
Subset of Paned — the passthrough-focused pane is maximized; acquiring zoom gives focus, losing focus returns it to Paned |
Doored |
Rendered as a door on the baseboard. DOM survival is a rendering decision, not part of this state: browser DOM retention follows parking (docs/specs/tiling-engine.md → "Parked leaves"); a terminal Surface unmounts its element (Registry: Orphaned) and remounts the same xterm on reattach — nothing replays |
Hidden |
In neither Pane nor Door — webview closed or mid-transition. A Surface in a hidden Workspace retains its Paned or Doored View state; Registry behavior follows docs/specs/layout.md → Workspace lifecycle. Process and Activity unaffected. |
| State | Meaning |
|---|---|
Cold |
First load of the webview; no handshake yet |
Live |
Handshake complete; events flowing from host to webview |
Resuming |
Webview just reopened; replay drain in progress |
Severed |
Webview closed while host retains the processes |
Transition rules in docs/specs/alert.md; the union is SessionStatus in lib/src/lib/alert-manager.ts (its quiesce half in quiesce-detector.ts):
WATCHING_DISABLED · NOTHING_TO_SHOW · MIGHT_BE_BUSY · BUSY · OSC_NOTIF_BUSY · COMMAND_EXIT_ARMED · MIGHT_NEED_ATTENTION · ALERT_RINGING
Only terminal Sessions run this machine (Panes and Surfaces).
| State | Meaning |
|---|---|
Clean |
In-memory state matches disk |
Dirty |
Changes pending |
Flushing |
Persistence write in flight |
A generation counter, not a literal enum; the save rule is docs/specs/layout.md → Session persistence.
A user verb is an intentional action that produces a single observable change.
| Verb | Effect |
|---|---|
spawn |
Create a new Session (Process: Absent → Live) |
kill |
Terminate a Surface. Terminal: Process Live/Exited → Tombstoned (Absent on standalone), Registry Mounted/Orphaned → Disposed. Browser: resource cleanup follows docs/specs/dor-browser.md → Placement And Lifetime. Either way View: any → Hidden. |
minimize |
Pane → Door (View: Paned → Doored) |
reattach |
Door → Pane (View: Doored → Paned) |
rename |
Update title; layer-agnostic |
zoom / unzoom |
Paned ↔ Zoomed |
swap |
Exchange two Surfaces' layout slots; ids travel with them, so Registry entries, Processes, and titles are untouched |
switchWorkspace |
Set the active Workspace (setActiveWorkspace), revealing its Wall and hiding the outgoing one. Terminal elements reattach; nothing resumes or restores; I8 holds by construction. |
createWorkspace |
Add a Workspace and mount its Wall, which spawns one pane; activate by default, unless activate: false. |
closeWorkspace |
kill each member Surface, then remove the Workspace; closing the last Workspace atomically creates a fresh replacement. |
renameWorkspace |
Update a Workspace's name; touches no Session |
moveWorkspace |
Reorder a Workspace within its Window; renames nothing and touches no Session |
transferWorkspace |
Move a Workspace to another Window, Surfaces and Sessions intact: release each member Session (detached, Process still Live) and resume it there. Kills nothing — not a closeWorkspace. |
tearOut |
transferWorkspace into a Window created for it. A Window whose last Workspace leaves closes itself. |
reopen |
Rebuild the newest reopenable close — a Surface, Workspace, or window — from its in-memory record as a new one: new Sessions and ids (docs/specs/reopen.md). Never a restore or reattach. |
Source of truth: lib/src/lib/workspace-store.ts; closeAll in lib/src/components/Wall.tsx; prepareWorkspaceTransfer in lib/src/components/wall/workspace-transfer.ts.
A system verb is a lifecycle transition driven by the runtime.
| Verb | Effect |
|---|---|
register / dispose |
Create / destroy a Registry entry |
release |
Destroy a Mounted or Orphaned Registry entry without killing its Live or Exited Process. Explicit Workspace transfer and refused-adoption cleanup only, never an unmount. |
mount / unmount |
Attach / detach the persistent DOM element (low-level op; the Registry entry survives unmount). A parked leaf stays mounted while Doored or Hidden (docs/specs/tiling-engine.md → "Parked leaves") |
exit |
Host observes process death (Process: Live → Exited) |
resume |
Webview reopens over retained PTYs (Link: Severed → Resuming → Live; Registry rebuilt from replay data; Process stays Live/Exited) |
restore |
Cold start from Snapshot (Link: Cold → Live; Process: Absent → Live with saved cwd; Registry rebuilt empty — scrollback is never persisted, so nothing replays; docs/specs/transport.md) |
tombstone |
Host marks a Session non-recoverable |
A Session is substitutable across most operations regardless of which states it occupies — every Session-facing API, Registry ops and PlatformAdapter PTY ops alike, declares its layer preconditions here:
| Category | Valid when | Examples |
|---|---|---|
| Universal | no layer-state gate | kill, rename, state queries |
| View-gated | View ≠ Hidden |
focusSession(id, true) |
| Process-gated | Process = Live |
writePty, resizePty |
| Registry-gated | Registry = Mounted |
refitSession |
| Terminal-gated | Surface has a terminal (Panes and Surfaces) | dor read / send / await, port scans |
| Browser-gated | Surface has a browser | browser nav / render-mode ops |
Must satisfy gated operations' layer preconditions at the call site or in the operation; universal operations have no layer-state gate. Missing Registry entries silently no-op, while dor capability gates return an error. Uniform typed precondition errors are staged (Future).
Source of truth: focusSession / refitSession in lib/src/lib/terminal-lifecycle.ts; requireTerminalSurface / requireBrowserSurface in lib/src/components/wall/use-dor-control.ts.
- I1:
SessionIdis immutable for the life of a Session and stable acrossresume/restore. - I2: Process state is independent of Registry, View, and Link. A
Liveprocess may beDooredorHidden; anExitedprocess may still bePaned. - I3: Must preserve Activity through the layout-only
minimize/reattachtransition. User engagement may acknowledge a ring (docs/specs/alert.md→ Engagement).ALERT_RINGINGfires only on a fresh transition, never onmountorreattach. - I4:
Registry: Orphanedoutlives no Session state exceptView: Dooredor a Surface in a hidden Workspace — at rest every other entry isMountedorDisposed, so anOrphanedentry that is neither is a leak. - I5:
killaccepts every layer state; a successful closure ends atView: Hidden. Closure guards belong todocs/specs/terminal-context.md→ Promotion and source closure anddocs/specs/dor-tool.md→ Closing unsaved Tools; per-kind effects are the User verbs row. - I6:
renameis universally valid including whenProcess = ExitedandView = Doored. - I7: Every Surface sits in exactly one Pane; every Pane and its Surfaces belong to exactly one Workspace; every Workspace belongs to one Window.
- I8: Must preserve Process and Activity during
switchWorkspace, without firing a fresh ring (I3;docs/specs/layout.md→ Workspaces). - I9: A Workspace's union status is a pure projection of its members' Activity: no independent state, destroyed with the Workspace.
- I10: Must preserve a terminal Surface's
SessionId(I1). Must keep a browser Surface's id across every render-mode swap and relaunch, swapping its renderer in place (docs/specs/dor-browser.md→ Display Modal And Render Swaps). Replacing an untouched shell — by a browser Surface or a host New Terminal — creates a new Surface with a new id (rationale).
Use glossary names instead. A left-column term retains meaning only where noted.
| Term | Status |
|---|---|
| detach | Retired: DOM-level op → unmount; user-level Pane→Door → minimize. |
| reconnect | Retired: live-PTY case → resume; cold start → restore. |
| restore | Keeps its cold-start rehydrate meaning. Never for Door→Pane (reattach), a closed Surface's rebuild (reopen), or alert-manager seeding (seed). |
| attach | Retired at the DOM layer (attachTerminal) → mount; user-level reattach (Door→Pane) keeps the re- prefix. |
| session | The durable identity of a terminal Surface. Never for the Activity projection (ActivityState, not SessionUiState), nor for a browser provider's lowercase session string (dormouse.<scope>.<key>) — not a Dormouse durable unit. |
| terminal | Keeps its meaning for the xterm.Terminal instance; prose meaning "the whole thing" is Session. |
| surface | Not retired. Session names only the terminal kind; Surface covers both. |
| panel / pane / leaf | Prefer pane for the layout slot; leaf is Lath's tree node for it (1:1). "panel" survives only in React component names (TerminalPanel, BrowserPanel, IframePanel, AgentBrowserPanel). |
| face | Retired — capabilities are named by the kinds: "console face" → terminal, "web face" → browser. Gate with hasTerminal / hasBrowser, never a face-set. |
| Host | Retired as the remote-control role → Burrow (Roles). Keeps the platform sense — the process behind the webview — plus the Host header, hostnames, and "self-host". |
| Server | Retired for the coordinating server → Relay. Keeps HTTP servers, net.Server, dev servers, and "self-host". |
| tether | Remote-control only (docs/specs/remote-api.md): a pane whose size a remote viewer holds has ceded terminal size authority to it — the Burrow's own pane shows the holder in its strip; another viewer's "tethering to <device>" display is staged. Never a layout term, never for Pane/Door relationships. |
Remote-only vocabulary (Viewer, and the wire-level DirectoryEntry projection of a pane) is defined in docs/specs/remote-api.md.
- Layer names and state names are
PascalCasenouns (Paned,Tombstoned). - Verbs are
camelCasein code and lowercase in prose (minimize, notMinimize). - Event kind strings match the verb:
'minimizeChange', not'detachChange'. - A persisted type is
Persisted<Shape>where<Shape>is the glossary noun (PersistedPane,PersistedDoor,PersistedWorkspace,PersistedWindow). - A handle type is
<Layer>State(ActivityState, notSessionUiState). - Surface kinds are lowercase strings; Panes and Surfaces is canonical for how persisted
surfaceType,renderMode, and CLIkind/render_moderelate. - Container names are
PascalCasenouns (Workspace,Window); a Workspace's id type isWorkspaceId.doraddresses a Window by its host label (window:<label>, orwindow:1on a single-Window host;docs/specs/dor-cli.md→ Handle Model);WindowIdremains reserved for a shared identity type. Container verbs suffix the container (createWorkspace,switchWorkspace), distinct from the layer-agnostic Sessionrename.
Scope: model-extensions — unordered:
- Typed precondition errors. The Liskov contract's enforcement: a gated call against the wrong state (e.g.
writePtyon a non-LiveProcess) fails with a typed error naming the violated precondition, replacing today's silent early return. - Dormouse Burrow — a headless Burrow. A Burrow with no local UI, so a machine nobody sits at can still be paired with. It changes no role: the same enrollment, ACL, and pairing approval, with the approval surfaced somewhere other than a Wall.