Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
342b0c1
specs: add tiling-engine.md, the design-stage dream spec for Lath
nedtwigg Jul 7, 2026
8ddff74
specs: make tiling-engine.md handoff-ready for a fresh implementer
nedtwigg Jul 7, 2026
2584448
specs: lath core is renderer-agnostic; HTML is one adapter
nedtwigg Jul 7, 2026
e5f62bd
lath: stage 1 core — pure split-tree model, layout, ops, restore tokens
nedtwigg Jul 7, 2026
5b50350
lath: stage 2a — pane props contract, dockview panel adapters
nedtwigg Jul 7, 2026
22b53f5
lath: stage 2b — wall store, dockview↔lath converters, LathHost binding
nedtwigg Jul 7, 2026
914ceda
lath: stage 2c — Wall renders through Lath behind dormouse.flags.lath
nedtwigg Jul 7, 2026
7e31ddc
specs: promote lath stage 2 above the fold; acceptance rows 1-10 veri…
nedtwigg Jul 7, 2026
0993a2a
lath: apply /simplify review across the lath work
nedtwigg Jul 7, 2026
6bcf573
lath: stage 3 — headless animator, native motion in LathHost
nedtwigg Jul 7, 2026
4911de7
lath: stage 4 — hierarchical pointer DnD, baseboard minimize, door dr…
nedtwigg Jul 7, 2026
f43f1b7
lath: /simplify pass on the stage-3 animator work
nedtwigg Jul 7, 2026
93e0d33
lath: /simplify pass on the stage-4 DnD work
nedtwigg Jul 7, 2026
667c4c2
website: port the playground tutorial off the dockview api
nedtwigg Jul 7, 2026
0940bec
lath: stage 5 — dockview removed; Lath is the engine
nedtwigg Jul 7, 2026
b79d9ca
lath: final /simplify across the whole delta
nedtwigg Jul 7, 2026
8e72f10
lath: throttle terminal refits during animated motion
nedtwigg Jul 7, 2026
bf95aed
lath: extract the dor control plane from Wall.tsx
nedtwigg Jul 7, 2026
bd61a12
lath: migrate legacy layouts once at the session read boundary
nedtwigg Jul 7, 2026
3593832
lath: extract the pane/Door drag controller from LathHost
nedtwigg Jul 7, 2026
2db1e28
Fix restore beside split siblings
nedtwigg Jul 7, 2026
21e94c8
Keep selection on swapped pane
nedtwigg Jul 7, 2026
1aaa9dd
Ignore zoom for minimized doors
nedtwigg Jul 7, 2026
9fe94cd
Delay terminal disposal until kill fade completes
nedtwigg Jul 7, 2026
d5d47cd
lath: record the swap partner in the Cmd-Arrow breadcrumb
nedtwigg Jul 7, 2026
d1153e8
Fix zoomed Lath kill fade
nedtwigg Jul 7, 2026
b62d8cd
Expire tutorial move suppression
nedtwigg Jul 7, 2026
ee2fe8c
Fix seed layout stacking every pane vertically
nedtwigg Jul 7, 2026
f096504
Make Chromatic terminal snapshots deterministic
nedtwigg Jul 7, 2026
becd30c
simplify: collapse settleTerminals onto waitForCondition; harden geom…
nedtwigg Jul 7, 2026
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
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ The primary job of a spec is to be an accurate reference for the current state o
Each spec's own `Files` / `Code Map` section is the exhaustive file→spec mapping; the touch points below are the load-bearing areas only.

- **`docs/specs/glossary.md`** — Canonical vocabulary: the **Surface** model (terminal **Session** / **browser** surface), the six Session layers and their states, the `Window ⊃ Workspace ⊃ Pane ⊃ Surface` hierarchy and Workspace union status, transition verbs, invariants I1–I10, and the Liskov contract on Registry APIs. Read this first; every other spec defers to it when naming a state, a surface kind, or a verb.
- **`docs/specs/layout.md`** — Tiling layout and its interaction model: panes/doors, dockview config, passthrough/command modes, command-mode keyboard dispatch, selection overlay, spatial navigation, minimize/reattach, inline rename, kill confirmation, session lifecycle + persistence recovery, pane animations, and the workspaces-rollout ledger (`## Future`). Touch points: `lib/src/components/Wall.tsx` + `components/wall/`, `Baseboard.tsx` / `Door.tsx` / `TerminalPane.tsx`, the registry/lifecycle/persistence modules in `lib/src/lib/`, and any keyboard/navigation/mode/workspace behavior.
- **`docs/specs/layout.md`** — Tiling layout and its interaction model: panes/doors, the Lath engine pointer, passthrough/command modes, command-mode keyboard dispatch, selection overlay, spatial navigation, minimize/reattach, inline rename, kill confirmation, session lifecycle + persistence recovery, pane animations, and the workspaces-rollout ledger (`## Future`). The tiling engine internals live in tiling-engine.md; layout.md owns the interaction model on top. Touch points: `lib/src/components/Wall.tsx` + `components/wall/`, `Baseboard.tsx` / `Door.tsx` / `TerminalPane.tsx`, the registry/lifecycle/persistence modules in `lib/src/lib/`, and any keyboard/navigation/mode/workspace behavior.
- **`docs/specs/shortcuts.md`** — Quick-reference table of every keyboard shortcut by mode/context. layout.md owns the behavior; update both when a binding changes.
- **`docs/specs/tiling-engine.md`** — **Lath**, Dormouse's in-house headless tiling engine (it replaced dockview-react; the dependency is gone). Owns the engine internals: the pure core (split-tree model + `layout()`, the op set with Door restore tokens, sash/neighbor/autoEdge geometry, the headless animator, hierarchical hit-testing), the never-re-parent LathHost adapter with native motion and depth-cycling pointer DnD, the wall store + engine, the pane props contract, and Lath-only persistence with a one-way legacy-blob migration reader. layout.md owns the interaction model on top. Touch points: `lib/src/lib/lath/`, the `lath-*` modules + `LathHost.tsx` in `lib/src/components/wall/`, `Wall.tsx`.
- **`docs/specs/alert.md`** — The Activity layer: WATCHING / protocol / command-exit alert tracks, attention model, TODO lifecycle, bell + TODO UI states, notification protocols (`OSC 9` / `9;4` / `99` / `777` / `BEL`) with sanitization and security rules, and the Workspace union projection. layout.md defers to it for all alert/TODO behavior. Touch points: `activity-monitor.ts`, `alert-manager.ts`, notification parsing in `terminal-protocol.ts`, the bell/TODO UI in pane headers and Doors.
- **`docs/specs/terminal-state.md`** — Per-Session semantic state: CWD, prompt/editing/running/finished lifecycle, command runs, title candidates + header derivation, grouping keys, and the keystroke fallback for shells without OSC integration. Touch points: `terminal-state.ts`, `terminal-state-store.ts`, semantic event parsing in `terminal-protocol.ts`, derived pane/door labels.
- **`docs/specs/terminal-escapes.md`** — Registry of every escape sequence Dormouse parses, answers, or deliberately ignores: OSC/CSI tables pointing at the owning spec, parsing location + `pty:data` strip semantics, replay-time report filtering, iTerm2 identity, and shell-integration injection. Touch points: OSC/CSI parsing at the PTY data boundary, `terminal-report-filter.ts`, `mouse-mode-observer.ts`, `standalone/sidecar/shell-integration/`, or adding any new escape sequence.
Expand Down
27 changes: 14 additions & 13 deletions docs/specs/dor-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> shared `dor` CLI, surface handle model, and host control plumbing this surface
> builds on.

Dormouse has one dockview component for web content: `BrowserPanel`, persisted as
Dormouse has one body component for web content: `BrowserPanel`, persisted as
`surfaceType: 'browser'` with a swappable `renderMode`.

Entry points:
Expand Down Expand Up @@ -60,9 +60,10 @@ Invariants:
navigations initiated by Dormouse chrome.
- Agent-browser session state is flat (`session`, `wsPort`, `binaryPath`,
`syncEngaged`, `key`), not nested.
- Every browser panel uses dockview `renderer: 'always'`, because moving iframe
DOM reloads it and moving the screencast canvas mid-click breaks click
synthesis.
- The browser DOM is never moved: Lath's leaf div is never re-parented, so an
embedded `<iframe>` never reloads and the screencast canvas never moves
mid-click (which would break click synthesis). This replaces the old dockview
`renderer: 'always'` workaround — the constraint is gone at the root, not healed.

Source of truth: `BrowserPanel.tsx`, `browser-surface.ts`, `Wall.tsx`
(`rendererForParams`, `replaceSurface`), `AgentBrowserPanel.tsx`
Expand Down Expand Up @@ -241,19 +242,19 @@ canvas, feeds params/visibility, forwards DOM input, and subscribes to one
snapshot via `useSyncExternalStore`. The controller owns one
`AgentBrowserConnection` for `{ session, streamPort, binaryPath }` paired with
its screenshot loop, and it is SURFACE-scoped rather than panel-scoped: it
survives panel unmount (minimize, dockview layout churn, React StrictMode). So
survives panel unmount (minimize, layout churn, React StrictMode). So
minimize no longer synchronously disposes the connection — the view detaches,
which counts as hidden and parks the connection after the ~1s debounce, reaching
the same zero-resource end state with less thrash. The agent-browser
daemon/session stays alive throughout and reattaches from persisted params. The
controller's client resources are released only at pane kill or a render swap
away from the renderer (`disposeAgentBrowserSurfaceController` in `Wall.tsx`).

Hidden-but-mounted panes park too. Browser panels use `renderer: 'always'`, so
an inactive dockview tab or a backgrounded window stays mounted and would keep
its ~20Hz stream plus per-pulse screenshot loop running for nothing. A pane that
goes off-screen — or whose view unmounts (minimize) — parks after a ~1s debounce
(so quick tab-flipping or a StrictMode remount doesn't thrash the connection):
Hidden-but-mounted panes park too. A Lath leaf is always mounted (no active-tab
gating), so a backgrounded window would keep its ~20Hz stream plus per-pulse
screenshot loop running for nothing. A pane that goes off-screen — or whose view
unmounts (minimize) — parks after a ~1s debounce (so a quick visibility flip or a
StrictMode remount doesn't thrash the connection):
the connection and screenshot loop are disposed while the daemon/session stays
alive, and daemon-side frame streaming stops on its own because clients trigger
it. Parking also clears the controller's "this stream port opened live" marker,
Expand Down Expand Up @@ -438,8 +439,8 @@ Source of truth: `IFRAME_SHIM` in
- `registerSurfaceFocusHandle` focuses/blurs the iframe element like other
surfaces.
- `IframePanel` applies `transform: translateZ(0)` to its immediate container to
avoid Chromium out-of-process iframe pointer offsets caused by dockview
containment.
avoid Chromium out-of-process iframe pointer offsets from a far-away
compositing/containing ancestor.
- The iframe sandbox omits `allow-top-navigation` to block framebusting while
allowing scripts, same-origin within the proxy origin, forms, popups, modals,
downloads, and common device/clipboard permissions.
Expand Down Expand Up @@ -513,7 +514,7 @@ Source of truth: `lib/src/lib/platform/types.ts`,
When changing browser-surface behavior:

- `renderMode` is canonical. Never reintroduce `surfaceType: 'iframe' | 'agent-browser'` or `poppedOut` as stored state — extend the `resolveRenderMode` migration instead, and update the kind-mapping table in `docs/specs/glossary.md` if adding a render mode.
- Every browser panel keeps dockview `renderer: 'always'` — moving iframe DOM reloads it, and moving the screencast canvas mid-click breaks click synthesis.
- Never move a browser surface's DOM. Lath's leaf div is never re-parented, so an `<iframe>` never reloads and the screencast canvas never moves mid-click (which would break click synthesis) — do not add any re-parenting/reordering that would defeat this.
- A new agent-browser subcommand must be added to `AGENT_BROWSER_ALLOWED_SUBCOMMANDS` (`lib/src/lib/platform/types.ts`); the host-side allowlist is the security boundary, not the CLI.
- External-binary spawns go through `spawnAndCapture` (`dor-lib-common`), never raw `child_process` — see `docs/specs/dor-cli.md` → Spawning External Binaries.
- New kill/swap/teardown paths must run `closeAgentBrowserSession` **and** dispose the surface controller (`disposeAgentBrowserSurfaceController`), and respect the closed-session mark so pop-out auto-revert cannot resurrect a killed session.
Expand Down
6 changes: 3 additions & 3 deletions docs/specs/dor-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Source of truth:
| Standalone staging/runtime env | `standalone/package.json`, `standalone/src-tauri/src/lib.rs`, `standalone/sidecar/pty-core.js`, `standalone/sidecar/main.js` |
| VS Code staging/runtime env | `vscode-ext/package.json`, `vscode-ext/src/pty-manager.ts`, `vscode-ext/src/pty-host.js` |
| Control request routing into the webview | `standalone/src/tauri-adapter.ts`, `vscode-ext/src/message-router.ts`, `lib/src/lib/platform/vscode-adapter.ts` |
| Implemented webview control handler | `lib/src/components/Wall.tsx` |
| Implemented webview control handler | `lib/src/components/wall/use-dor-control.ts` (the `useDorControl` hook, mounted by `lib/src/components/Wall.tsx`) |

## Bundling And PATH

Expand Down Expand Up @@ -203,15 +203,15 @@ Invariants:
## Current Implemented Commands

Implemented commands call private `surface.*` control methods. `surface.list`
derives its response from current Dockview panels plus terminal state/activity
derives its response from the current visible panes plus terminal state/activity
snapshots where available, then returns `workspace:1` and `window:1` — it
reports the single active Workspace (Workspace-aware tagging is staged; see
[Future](#future)).

Command tails captured after `--` are sent as raw argv arrays (`command:
string[]`); the host — not `dor` — quotes them for the target shell. `dor`
cannot know which shell the target surface runs, so it forwards argv unquoted
and the host (`lib/src/components/Wall.tsx`, `dorCommandString`) detects the
and the host (`lib/src/components/wall/use-dor-control.ts`, `dorCommandString`) detects the
target shell, picks a quoting style with
[`shellCommandKind` / `buildShellCommandForKind`](../../dor/src/commands/shell-quote.ts)
(`cmd` / `posix` / `powershell`), and renders a single command string used for
Expand Down
12 changes: 6 additions & 6 deletions docs/specs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The unit of *layout* is a **Pane**; the unit of *content* is a **Surface**.

| Term | Is | Owner |
|---|---|---|
| **Pane** | A slot in the tiling layout (one dockview panel). Holds one or more Surfaces, exactly one selected. | `lib/src/components/Wall.tsx` (dockview) |
| **Pane** | A slot in the tiling layout (one Lath leaf). Holds one Surface. | `lib/src/components/Wall.tsx`; the engine per `docs/specs/tiling-engine.md` |
| **Surface** | The content in a Pane: a **terminal** (a Session) or a **browser** Surface. | `Wall.tsx`; browser surfaces per `docs/specs/dor-browser.md` |

Today every Pane holds exactly one Surface, but the model reserves multiple Surfaces per Pane (a future in-pane surface strip) — which is why a Surface ref (`surface:N`) is distinct from a Pane ref (`pane:N`).
Expand Down Expand Up @@ -50,7 +50,7 @@ A **Session** runs the full six-axis model below. A **browser Surface** particip
| **Activity** | full machine (ring + TODO) | **TODO only** — a user may flag it; it never auto-rings (no BEL/OSC source) |
| **Snapshot** | scrollback, cwd, alert | its `surfaceType` + render params (`docs/specs/transport.md`) |
| **Process** | PTY (`Live`/`Exited`/`Tombstoned`/`Absent`) | none — host lifetime is the agent-browser session or proxy grant, keyed outside the Pane id |
| **Registry** | xterm Terminal + persistent DOM element | none — DOM owned by dockview (`renderer:'always'`); focus via a lightweight handle |
| **Registry** | xterm Terminal + persistent DOM element | none — DOM hosted by LathHost's leaf div (never re-parented); focus via a lightweight handle |
| **Link** | resume / replay over the live PTY | none — rebuilt from persisted params, not replayed |

The containment hierarchy the `dor` CLI handle model commits to (`docs/specs/dor-cli.md`):
Expand All @@ -59,7 +59,7 @@ The containment hierarchy the `dor` CLI handle model commits to (`docs/specs/dor
Window ⊃ Workspace ⊃ Pane ⊃ Surface (terminal = Session | browser)
```

**Surface identity:** a Surface's id is its dockview panel id. For a terminal Surface that id *is* the `SessionId` and is stable (I1). A browser Surface's id is **not** preserved across a render-mode swap: switching `iframe` ⇄ `ab-screencast` ⇄ `ab-popout` replaces the Surface in the same Pane slot (new id), preserving only the slot and target URL (`docs/specs/dor-browser.md`).
**Surface identity:** a Surface's id is its Lath leaf id. For a terminal Surface that id *is* the `SessionId` and is stable (I1). A browser Surface's id is **not** preserved across a render-mode swap: switching `iframe` ⇄ `ab-screencast` ⇄ `ab-popout` replaces the Surface in the same Pane slot (new id), preserving only the slot and target URL (`docs/specs/dor-browser.md`).

## Containers

Expand All @@ -68,7 +68,7 @@ A Surface never floats free: it sits in a **Pane**, every Pane belongs to exactl
| Container | Holds | Owner |
|---|---|---|
| **Window** | One or more Workspaces. The OS frame (the standalone Tauri window) or the host frame (a VS Code window). | host (Tauri / VS Code) |
| **Workspace** | A named set of Panes and their Surfaces, plus the layout that arranges them (dockview snapshot + doors). Exactly one **Wall** renders one Workspace. | `lib/src/components/Wall.tsx` at render time; persisted per `docs/specs/transport.md` |
| **Workspace** | A named set of Panes and their Surfaces, plus the layout that arranges them (Lath layout snapshot + doors). Exactly one **Wall** renders one Workspace. | `lib/src/components/Wall.tsx` at render time; persisted per `docs/specs/transport.md` |

A **Workspace** is the durable grouping a user thinks of as "a window's worth of panes." It has a `WorkspaceId`, a user-facing `name`, the Surfaces it contains, and the layout that arranges them. The pre-workspace model had exactly one implicit Workspace per Window; the model now allows several.

Expand Down Expand Up @@ -139,7 +139,7 @@ A **Session** is the tuple of its `SessionId` plus one state per layer. `Session

| State | Meaning |
|---|---|
| `Paned` | Rendered as a pane in the content area (dockview group) |
| `Paned` | Rendered as a pane in the content area (a Lath leaf) |
| `Zoomed` | Subset of `Paned` — the selected pane is maximized |
| `Doored` | Rendered as a door on the baseboard |
| `Hidden` | In neither pane nor door — the webview is closed, the Surface belongs to an inactive Workspace (standalone), or the Surface is mid-transition. Process and Activity are unaffected. |
Expand Down Expand Up @@ -241,7 +241,7 @@ Use glossary names instead of these. The left column retains a meaning only wher
| **session** | The durable identity of a **terminal Surface**. Do not use it for the Activity projection (that is `ActivityState`, not `SessionUiState`), nor for the agent-browser daemon's lowercase `session` string (`dormouse.1.<key>`), which is not a Dormouse durable unit. |
| **terminal** | Keeps its meaning for the `xterm.Terminal` instance. Prose meaning "the whole thing" is **Session** (a terminal Surface). |
| **surface** | A glossary term, not retired: the durable occupant of a Pane (a terminal Session or a browser Surface). Use **Session** only for the terminal kind; use **Surface** when a statement holds for both. |
| **panel / pane** | Prefer **pane** for the layout slot. Use "panel" only when quoting dockview's own API (`api.panels`, `addPanel`). |
| **panel / pane / leaf** | Prefer **pane** for the layout slot; **leaf** is Lath's tree node for the same thing (they map 1:1). "panel" survives only in legacy persisted-blob field names. |
| **tether** | Remote-control term only: a display showing "tethering to \<device\>" has ceded terminal size authority to a remote viewer (`docs/specs/remote-api.md`). Not a layout term — do not use it for Pane/Door relationships. |

Remote-only vocabulary (**Viewer**, the wire-level `DirectoryEntry` projection) is defined in `docs/specs/remote-api.md` § Terminology.
Expand Down
Loading
Loading