Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ The system uses **raised surfaces**, not "cards." There are no nested cards. The
### Navigation

The system has no traditional product top-nav. Three surfaces play navigational roles:
- **Workspace strip** (standalone app bar, top): horizontal tabs, one per Workspace, for switching between Workspaces within one window. Inactive tabs carry the union alert/TODO indicators (bell + TODO pill) borrowed from the Door vocabulary; the active tab carries none. This is standalone app-bar chrome around the Wall — see `docs/specs/layout.md` and `docs/specs/alert.md` — and its exact visual treatment is being designed in Storybook. VS Code surfaces the same status on its own native tab/badge chrome instead (`docs/specs/vscode.md`).
- **Workspace strip** (standalone app bar, top): horizontal tabs, one per Workspace, for switching between Workspaces within one window. The active tab takes the wall's own palette and the terminal top radius — a tab is the top of its Workspace as a Door is the bottom of its Surface — and carries no indicators; an inactive tab is transparent and carries the union alert/TODO indicators (bell + TODO pill) borrowed from the Door vocabulary. This is standalone app-bar chrome around the Wall — see `docs/specs/layout.md` and `docs/specs/alert.md`. VS Code surfaces the same status on its own native tab/badge chrome instead (`docs/specs/vscode.md`).
- **Baseboard** (bottom of the app): horizontal strip of doors representing minimized panes plus chrome action buttons. Doors are the primary navigation affordance to a minimized terminal. Buttons use `chromeButton` with 24px height, muted text, and `hover:text-foreground`; Settings icons use square buttons with 2px gaps, while labeled overflow buttons keep horizontal padding.
- **Pane Header (TerminalPaneHeader)**: the tab-replacing strip at the top of each pane. Lath is a headless tiling engine with no tab-bar chrome of its own; the React header IS the tab.

Expand Down
7 changes: 4 additions & 3 deletions docs/specs/alert.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,17 +332,18 @@ Source of truth: `lib/src/components/SettingsDialog.tsx`; `SettingsPreview` in `
| `ringing` | Any member Session is `ALERT_RINGING`. |
| `todo` | Any member Surface has `todo === true`. |
| `count` | Number of members ringing or TODO; each Surface counts once. |
| `ringSeq` | The largest member `ringSeq`; read only for change, so a new ring replays the indicator's burst and returning to the Workspace does not. |

**Must keep the projection display-only:** it never enters the Activity machine or fires its own ring. A Surface with no activity entry contributes nothing. Callers **must include** minimized (`Doored`) Surfaces.

Reserved: **Must include inactive Workspaces' Surfaces when projecting their unions** (`docs/specs/layout.md` → Future, workspaces-rollout).
**Must project every Workspace, active or not.** The Activity store spans the whole Window, so what scopes it to one Workspace is the membership each mounted Wall publishes — panes ∪ doors, on every layout commit.

Source of truth: `computeWorkspaceUnion` in `lib/src/lib/workspace-union.ts`; `lib/src/lib/workspace-union.test.ts`.
Source of truth: `computeWorkspaceUnion` in `lib/src/lib/workspace-union.ts`; `setWorkspaceSurfaces` in `lib/src/lib/workspace-surfaces.ts`; `lib/src/lib/workspace-union.test.ts`.

Where it surfaces is host-specific:

- **VS Code** reflects the terminal portion onto native chrome — `docs/specs/vscode.md`, which also owns why browser-surface TODO stays webview-local.
- **Standalone** shows terminal rings/TODOs on panes and doors, and a browser Surface's `todo` on its own door. The workspace-strip union indicators are staged with the strip — `docs/specs/layout.md` `## Future` (workspaces-rollout).
- **Standalone** shows terminal rings/TODOs on panes and doors, and a browser Surface's `todo` on its own door. A **hidden** Workspace's tab additionally carries its union's TODO pill and bell, with `count` in the tab's accessible name; the visible Workspace's tab carries none, its panes and doors already saying it (`WorkspaceStrip` in `lib/src/components/WorkspaceStrip.tsx`).

## UI Contract

Expand Down
11 changes: 7 additions & 4 deletions docs/specs/dor-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,13 @@ Source of truth: `lib/src/components/wall/SurfacePaneHeader.tsx`,
## Dev-Server Chip

For loopback URLs (`localhost`, `*.localhost`, `127.0.0.1`, `::1`) the header
registers interest in the port. The Wall scans terminal panes and minimized doors
via `PlatformAdapter.getOpenPorts(id)` and **shows a chip only when exactly one
terminal owns that port**; zero or two-plus leave it unsettled, so a dev server
that starts later still matches. **Match only binds that serve localhost** —
registers interest in the port. **One scan loop per Window, over every mounted
Wall's terminal panes and minimized doors** — each Wall registers its Surfaces as
a candidate source, since the wanted-port store and the resolutions are
window-wide. It reads `PlatformAdapter.getOpenPorts(id)` and **shows a chip only
when exactly one terminal owns that port**; zero or two-plus leave it unsettled,
so a dev server that starts later still matches, and a Wall arriving or leaving
re-validates what had settled. **Match only binds that serve localhost** —
loopback or any-interface (`0.0.0.0`, `::`), never a specific non-loopback bind.
Scanning is debounced, idle-scheduled, and polls only while a wanted port is
unmatched; reload revalidates optimistically.
Expand Down
41 changes: 24 additions & 17 deletions docs/specs/dor-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,18 +282,28 @@ Invariants:
- Text list output defaults to refs; commands that list handles accept
`--id-format refs|ids|both` (`uuids` is a compatibility alias for `ids`). JSON
list output always includes both refs and stable ids.
- Reserved: `workspace:<n>` (and `workspace:<name>` when exactly one Workspace
matches) and `window:<n>` select a container. The grammar is reserved now so
Surface refs never collide with it; the flag and the commands consuming it are
staged — see [Future](#future). The webview handler already rejects any
workspace/window target other than the singleton `workspace:1` / `window:1`.
Today's handler resolves stable Surface ids within the mounted Workspace;
cross-Workspace routing is staged with Workspace-aware listing/targeting.
Cross-window duplicate ids follow `docs/specs/vscode.md` → "Peer surfaces
across windows".

Source of truth: `dor/src/commands/shared.ts`, `dor/src/commands/types.ts`, and
`surfaceRefForId` / `transferSurfaceRef` in `lib/src/components/Wall.tsx`.
- `workspace:<n>` selects a container and is **positional**, so a strip reorder
renumbers it; `workspace:<name>` is the stable handle and is staged with the
`dor workspace` commands (see [Future](#future)). `window:<n>` is rejected for
every `n` but 1. **Every Workspace has a `surface:1`**, so a Surface ref alone
never identifies a Workspace.
- **One Wall answers each request**, resolved in order: an explicit
`workspace:<n>`, else the Workspace owning the calling Surface, else the
active one; nothing mounted leaves the request unanswered, after a bounded
retry that covers the tick between a Workspace being created and its Wall
registering. **Every request is answered, including a container ref of the
wrong type and a handler that throws** — an unanswered one blocks its caller
to the deadline. A Workspace being closed refuses the Surface-creating verbs
(`docs/specs/layout.md` → "Workspaces"). **Surface targets
resolve within the answering Workspace** — refs are Workspace-scoped — so a
`dor split` from a background Workspace lands beside its caller rather than
wherever the user is looking. Cross-Workspace targeting is staged with
Workspace-aware listing. Cross-window duplicate ids follow
`docs/specs/vscode.md` → "Peer surfaces across windows".

Source of truth: `dor/src/commands/shared.ts`, `dor/src/commands/types.ts`,
`surfaceRefForId` / `transferSurfaceRef` in `lib/src/components/Wall.tsx`, and
`resolveDorControlRoute` in `lib/src/components/wall/dor-control-router.ts`.

## Current Implemented Commands

Expand All @@ -302,8 +312,8 @@ in `dor/src/protocol.ts` (`SURFACE_CONTROL_METHODS`)** so the emitting client
and the dispatching webview cannot drift. `surface.list` joins the current
Workspace's Surfaces — visible panes **plus minimized (doored)** ones, each
tagged `view` (`paned` / `zoomed` / `minimized`) — with terminal state and
activity snapshots, and reports the single active Workspace as `workspace:1` /
`window:1` (Workspace-aware tagging is staged; see [Future](#future)). Per the
activity snapshots, and reports the answering Workspace's own `workspace:<n>`
alongside `window:1`. Per the
visible-vs-listed split [Handle Model](#handle-model) states, **a visible split
reference adds a pane in Lath, a minimized one a sibling Door in the
baseboard.** **`dor list` rows sort by the Workspace-stable `surface:N` ref**, a
Expand Down Expand Up @@ -583,6 +593,3 @@ Source of truth: `buildDorSurfacesInternal` in `lib/src/components/Wall.tsx`; `d
Like every command they ship with snapshot-tested help and the control methods
that back them, not ahead of them. Staged with the workspaces rollout
(`docs/specs/layout.md` `## Future`, workspaces-rollout).
- **Workspace-aware `surface.list`** — tags each surface with its real
`workspace:<n>` / `window:<n>` membership instead of reporting the single
active Workspace.
21 changes: 11 additions & 10 deletions docs/specs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Workspace and Window are containers, not Session layers — they group Surfaces

How many Workspaces a Window shows at once is host-specific:

- **Standalone** renders one implicit Workspace. Multiple-Workspace presentation is staged (`docs/specs/layout.md` → Future, workspaces-rollout).
- **Standalone** mounts every Workspace's Wall at once and shows one, switching between them (`docs/specs/layout.md` → Workspaces).
- **VS Code** maps one Workspace to one webview, several visible at once: the sidebar/panel `WebviewView` is the default Workspace, each `dormouse.open` editor-tab `WebviewPanel` an independent one owning its Sessions' PTYs and browser Surfaces (`docs/specs/vscode.md`).

### Wall chrome
Expand All @@ -88,7 +88,7 @@ A Workspace's **union status** is its display projection of member Surfaces' Act

### Implementation status

The Pane / Surface model and surface kinds are live. The Workspace model is unwired; `dormouse.flags.workspaces` controls the dormant standalone Window wrapper (`docs/specs/layout.md` → Workspaces), so the app runs one implicit Workspace. Ledger: `docs/specs/layout.md` `## Future` (**Scope: workspaces-rollout**); this glossary does not track it.
The Pane / Surface model, surface kinds, and the Workspace model are live; a Window still means one OS window, and `dormouse.flags.workspaces` still controls the stored Window wrapper (`docs/specs/layout.md` → Workspaces). Ledger: `docs/specs/layout.md` `## Future` (**Scope: workspaces-rollout**); this glossary does not track it.

## Roles

Expand Down Expand Up @@ -143,7 +143,7 @@ A **Session** is the tuple of its `SessionId` plus one state per layer (I1).
|---|---|
| `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 (I4) |
| `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 |

### View
Expand All @@ -153,7 +153,7 @@ A **Session** is the tuple of its `SessionId` plus one state per layer (I1).
| `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; inactive-Workspace presentation is staged (`docs/specs/layout.md` → Future). Process and Activity unaffected. |
| `Hidden` | In neither pane nor door — webview closed or mid-transition. A Surface in a hidden Workspace is **not** `Hidden`: it stays `Paned` or `Doored`, mounted and live. Process and Activity unaffected. |

### Link

Expand Down Expand Up @@ -197,12 +197,13 @@ A user verb is an intentional action that produces a single observable change.
| `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 model's active Workspace (`setActiveWorkspace`); no Surface or rendering change yet. |
| `createWorkspace` | Add Workspace metadata; activate by default, unless `activate: false`. |
| `closeWorkspace` | Remove Workspace metadata; the last remaining Workspace cannot be closed. |
| `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; the last remaining Workspace cannot be closed. |
| `renameWorkspace` | Update a Workspace's `name`; touches no Session |
| `moveWorkspace` | Reorder a Workspace within its Window; renumbers the positional `workspace:<n>` refs and touches no Session |

Source of truth: `setActiveWorkspace` / `createWorkspace` / `closeWorkspace` / `renameWorkspace` in `lib/src/lib/workspace-store.ts`; Surface lifecycle integration is staged in `docs/specs/layout.md` → Future, workspaces-rollout.
Source of truth: `setActiveWorkspace` / `createWorkspace` / `closeWorkspace` / `renameWorkspace` / `moveWorkspace` in `lib/src/lib/workspace-store.ts`; `closeAll` in `lib/src/components/Wall.tsx`.

### System verbs

Expand Down Expand Up @@ -239,11 +240,11 @@ Source of truth: `focusSession` / `refitSession` in `lib/src/lib/terminal-lifecy
- I1: `SessionId` is immutable for the life of a Session and stable across `resume` / `restore`.
- I2: Process state is independent of Registry, View, and Link. A `Live` process may be `Doored` or `Hidden`; an `Exited` process may still be `Paned`.
- I3: Activity state survives `minimize` / `reattach`. `ALERT_RINGING` fires only on a *fresh* transition, never on `mount` or `reattach`.
- I4: `Registry: Orphaned` outlives no Session state except `View: Doored` — at rest every other entry is `Mounted` or `Disposed`, so an `Orphaned` entry that is not `Doored` is a leak.
- I4: `Registry: Orphaned` outlives no Session state except `View: Doored` or a Surface in a hidden Workspace — at rest every other entry is `Mounted` or `Disposed`, so an `Orphaned` entry that is neither is a leak.
- I5: `kill` is universally valid and always ends at `View: Hidden`; its per-kind effects are the [User verbs](#user-verbs) row.
- I6: `rename` is universally valid including when `Process = Exited` and `View = 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: Reserved: **Must preserve Process and Activity during `switchWorkspace`, without firing a fresh ring on mount** (I3; `docs/specs/layout.md` → Future, workspaces-rollout).
- I8: **Must preserve Process and Activity during `switchWorkspace`, without firing a fresh ring** (I3). A switch reattaches terminal elements but resumes and restores nothing, so no ring can fire (`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 transfer the `surface:N` CLI ref when replacing a browser Surface**, minting a new id in the same layout slot with its target URL. An `ab-screencast` ⇄ `ab-popout` relaunch keeps the Surface id; render-mode changes do not universally imply replacement (rationale; `docs/specs/dor-browser.md` → Display Modal And Render Swaps).

Expand Down
Loading