Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ff60430
Adopt the spec-lifecycle convention across all specs
nedtwigg Jul 4, 2026
52a7734
Complete the AGENTS.md spec index and merge the structure sections
nedtwigg Jul 4, 2026
29575bd
Deduplicate restated invariants across specs
nedtwigg Jul 4, 2026
3974366
Glossary owns mode names and the surface-kind enum mapping
nedtwigg Jul 4, 2026
331a9f7
Fix the VR snapshot hierarchy and retire the POC framing
nedtwigg Jul 4, 2026
2c9b50a
Rewrite remote-security-model.md to describe the shipped system
nedtwigg Jul 4, 2026
0aaafa1
Anchor cross-spec section refs; add maintainer checklists
nedtwigg Jul 4, 2026
7253441
Split the durable mobile component spec out of the prototype doc
nedtwigg Jul 4, 2026
25e87b7
Add standalone.md — the missing host spec
nedtwigg Jul 4, 2026
c9626ce
Give CWD precedence its own subsection
nedtwigg Jul 4, 2026
31ece14
Load AGENTS.md in every Claude Code session; link PRODUCT.md
nedtwigg Jul 4, 2026
eefe416
Document the per-surface alarm tint in theme.md; fix DESIGN.md's desc…
nedtwigg Jul 4, 2026
b64798d
Enforce the spec conventions mechanically with scripts/spec-lint.mjs
nedtwigg Jul 4, 2026
accfd8d
Merge remote-tracking branch 'origin/main' into spec-refinement
nedtwigg Jul 4, 2026
402ec81
Spec-vs-code verification sweep: fix 30 drifted claims across 14 specs
nedtwigg Jul 4, 2026
4b29ac4
Delete the never-emitted terminal.resize/terminal.semantic events
nedtwigg Jul 4, 2026
e59318b
Resolve the high-confidence code-side findings from the sweep
nedtwigg Jul 5, 2026
bda2824
Require fresh presence for pairing (server-attested, 30s window)
nedtwigg Jul 5, 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
84 changes: 48 additions & 36 deletions AGENTS.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ This system has no "primary" accent in the brand sense. The closest analogue is
- **Terminal Background / Foreground** (`var(--vscode-terminal-background)` / `var(--vscode-terminal-foreground)`): the terminal content surface and xterm default text. Orthogonal to the chrome.
- **Error** (`var(--vscode-terminal-ansiRed)`): destructive actions and kill-confirm letter flash.
- **Success** (`var(--vscode-terminal-ansiGreen)`): TODO check, theme-store install confirm.
- **Alarm** (`var(--vscode-terminal-ansiYellow)` initial; runtime-rotated): bell-ringing alert tint. Per-surface OKLCH hue-rotation by `use-dynamic-palette.ts` from the bg the bell sits on, so the alert pops off any header.
- **Alarm** (`var(--vscode-terminal-ansiYellow)` baseline; runtime-overridden): bell-ringing alert tint. `computeDynamicPalette()` replaces each `--color-alarm-vs-*` token with plain white or black by the OKLab lightness of the bg the bell sits on (active header, inactive header, or door), so the ringing bell stays maximally legible on any surface.

### Fixed Exception
- **Window Close Hover** (`#b92a1b`): the only literal color in the whole system. Native OS close-button hover on Windows/Linux chrome buttons; matches the platform convention across themes.
Expand Down
29 changes: 20 additions & 9 deletions docs/specs/alert.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ These do not count as attention: mere visibility, command-mode selection, hover,

Attention is lost when the attention timer expires, the app loses focus, the attended Session is minimized or destroyed, or another Session becomes attended. `T_USER_ATTENTION` also acts as the minimum runtime for command-exit alerts.

Source of truth: `cfg.alert` in `lib/src/cfg.ts` defines `T_USER_ATTENTION` and the other timer defaults and their purpose.

## WATCHING Track

WATCHING is the user-controlled output/silence monitor. It starts fresh when enabled and is disposed when disabled. Meaningful output excludes resize redraw noise during `T_RESIZE_DEBOUNCE`; theme changes, remounts, DOM reparenting, selection, and focus changes are not output.
Expand All @@ -61,8 +63,6 @@ WATCHING is the user-controlled output/silence monitor. It starts fresh when ena
| `MIGHT_NEED_ATTENTION` | A busy Session went quiet. Debounce state. |
| `ALERT_RINGING` | WATCHING observed likely completion while the Session lacked attention. |

Source of truth: `cfg.alert` in `lib/src/cfg.ts` defines timer defaults and their purpose.

Source of truth: `ActivityMonitor` in `lib/src/lib/activity-monitor.ts` implements the transitions. The invariants the implementation must honor:

- Output drives the monitor up the chain `NOTHING_TO_SHOW` -> `MIGHT_BE_BUSY` -> `BUSY`; silence drives it down `BUSY` -> `MIGHT_NEED_ATTENTION` -> `ALERT_RINGING`. The `MIGHT_*` states are debounce windows in both directions.
Expand Down Expand Up @@ -135,10 +135,10 @@ Supported keys:

`title` and `body` chunks append to the pending notification. Completion rings once if the sanitized title or body is nonempty. If `i` is omitted, only a complete single-sequence notification is meaningful.

Management payloads do not ring:
Management payloads contribute no notification content:

- `p=?` sends a support response advertising the support payload defined in `lib/src/lib/terminal-protocol.ts` (`OSC99_SUPPORT_PAYLOAD`).
- `p=close`, `p=alive`, `p=icon`, and `p=buttons` are consumed or ignored without creating notification UI.
- `p=close`, `p=alive`, `p=icon`, and `p=buttons` are consumed or ignored without creating notification UI of their own. Like any chunk, one carrying the (default) `d=1` done flag still completes a pending same-`i` notification, which may then ring on its previously accumulated title/body — kitty's done-flag semantics apply regardless of the final chunk's payload type.

Source of truth: `lib/src/lib/terminal-protocol.ts` defines the pending OSC 99 chunk TTL and max-pending-id cap.

Expand Down Expand Up @@ -177,7 +177,7 @@ Clearing behavior:

> See `docs/specs/glossary.md` for the Workspace / Window containers.
>
> **Partially implemented.** The union *projection* is a pure function — `computeWorkspaceUnion(surfaceIds, activitySnapshot)` in `lib/src/lib/workspace-union.ts` (stage 2b). **VS Code surfacing is implemented**: the editor-tab title and the bottom-panel view badge reflect the union (terminal ring/TODO only; `docs/specs/vscode.md`). Still not built: the **standalone strip indicators** (stage 3). A browser Surface's user-set `todo` round-trips to the activity store live and persists across restart (stage 2a), so it is counted by the projection and shows on the surface's door today.
> The union *projection* is a pure function — `computeWorkspaceUnion(surfaceIds, activitySnapshot)` in `lib/src/lib/workspace-union.ts`. **VS Code surfacing is implemented**: the editor-tab title and the bottom-panel view badge reflect the union (terminal ring/TODO only; `docs/specs/vscode.md`). A browser Surface's user-set `todo` round-trips to the activity store live and persists across restart, so it is counted by the projection and shows on the surface's door today. The **standalone strip indicators** are staged — see `docs/specs/layout.md` `## Future` (workspaces-rollout).

A Workspace projects a **union status** over the attention state of the Surfaces it contains (terminal Sessions and browser Surfaces alike — see `docs/specs/glossary.md`):

Expand All @@ -193,10 +193,8 @@ Rules:

Where the union surfaces is host-specific:

- **Standalone:** each inactive Workspace's tab in the strip shows the union `ringing` bell and `todo` pill, reusing the Door indicator vocabulary (`bellIconClass`, the TODO pill). The **active** Workspace's tab shows no union indicator — its rings and TODOs are already visible on its own panes and doors. See `docs/specs/layout.md`.
- **VS Code:** the host reflects the **terminal** portion of each Workspace's union onto the webview's native chrome — an editor tab's icon (and optionally title) and the sidebar view's numeric badge. Browser-surface TODO stays webview-local and is not shown on native chrome until a future webview→host Surface-state channel exists. See `docs/specs/vscode.md`.

This spec fixes the projection and the surfacing rules; the exact visual treatment of the standalone strip is settled in the Storybook UI pass.
- **Standalone:** the workspace strip that will show per-tab union indicators is staged with the rest of the strip UI — the behavioral rules live in `docs/specs/layout.md` `## Future` (workspaces-rollout). Today a browser Surface's `todo` shows on its own door, and terminal rings/TODOs show on their panes and doors as specified above.
- **VS Code:** the host reflects the **terminal** portion of each Workspace's union onto the webview's native chrome — an editor tab's icon (and optionally title) and the sidebar view's numeric badge. Browser-surface TODO stays webview-local; surfacing it on native chrome needs the webview→host Surface-state channel staged in `docs/specs/vscode.md` `## Future`. See `docs/specs/vscode.md`.

## UI Contract

Expand Down Expand Up @@ -265,6 +263,19 @@ Security requirements:
- Bell, TODO, preview, and dialog controls must remain keyboard reachable; dialogs trap focus and support `Escape`.
- Tooltips, dialog copy, and future localized TODO labels must wrap in narrow layouts.

## Files

| File | Role |
|------|------|
| `lib/src/lib/activity-monitor.ts` | Per-Session WATCHING state machine (output/silence timers) |
| `lib/src/lib/alert-manager.ts` | `AlertManager`: protocol + command-exit tracks, attention, TODO, notification storage, status projection |
| `lib/src/lib/terminal-protocol.ts` | Notification/progress OSC parsing (`OSC 9` / `9;4` / `99` / `777`, BEL), sanitization limits, OSC 99 chunk state |
| `lib/src/lib/session-activity-store.ts` | React activity snapshot store, primed alert state, platform delegates |
| `lib/src/lib/workspace-union.ts` | `computeWorkspaceUnion` projection |
| `lib/src/components/bell-icon-class.ts` | Bell tilt/animation mapping from public status |
| `lib/src/components/wall/TerminalPaneHeader.tsx` | Bell button, TODO pill, notification preview and dialog |
| `lib/src/components/Door.tsx` | Door bell + TODO display |

## References

- iTerm2 proprietary escape codes (OSC 9, OSC 9;4): https://iterm2.com/documentation-escape-codes.html
Expand Down
6 changes: 4 additions & 2 deletions docs/specs/auto-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ app launch
├─ check for post-install markers in localStorage
│ ├─ success marker → show "Updated to vX.Y.Z" banner (auto-dismisses after 10s)
│ ├─ failure marker → show "Update failed." banner with debug action
│ ├─ failure marker → show "Update failed" banner with debug action, then STOP —
│ │ the update check is skipped this launch (re-prompting for the version that
│ │ just failed would unmount an open debug dialog)
│ └─ no marker → continue
├─ wait 5 seconds
Expand Down Expand Up @@ -61,7 +63,7 @@ When a notice has follow-up actions, it uses ` · ` as the separator between the

All states are dismissible via [×]. Dismissing an unapproved `available` notice means no update is downloaded or installed in that session. Dismissing a `downloading` or `downloaded` notice hides it for the session only — it does not cancel an already-approved download/install.

The notice matches the Baseboard's existing text style (9px mono, `text-muted`). It's pushed right via `ml-auto` so it doesn't compete with doors or the shortcut hint on the left.
The notice matches the Baseboard's existing text style (`text-sm font-mono text-muted` — 12px via the theme.css `text-sm` override). It's pushed right via `ml-auto` so it doesn't compete with doors or the shortcut hint on the left.

### Threading

Expand Down
4 changes: 2 additions & 2 deletions docs/specs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ If you edit `CHANGELOG.md` manually outside `/release-notes` and want to preview
| `OVSX_PAT` | `vscode-extension-publish` GitHub environment secret | OpenVSX publish |
| `GITHUB_TOKEN` | GitHub Actions (automatic) | Artifact upload |
| `APPLE_SIGNING_IDENTITY` | Local keychain | macOS codesign |
| `APPLE_ID` | Local env / prompted | Notarization |
| `APPLE_ID` | Hardcoded in `sign-and-deploy.sh` | Notarization |
| `APPLE_SIGN_PASS` | Local env / prompted | Notarization password |
| `APPLE_TEAM_ID` | Local env / hardcoded | Notarization |
| `EV_SIGN_PIN` | Local env / prompted | Windows PIV signing |
| `TAURI_SIGNING_PRIVATE_KEY` | Local env | Tauri update signatures |
| `TAURI_SIGNING_PRIVATE_KEY` | Local env / prompted | Tauri update signatures |
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Local env / prompted | Tauri update key password |
24 changes: 20 additions & 4 deletions docs/specs/dor-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ Surface lifetime owns backing resources:
- Killing or swapping an agent-browser pane also disposes its surface controller
(`disposeAgentBrowserSurfaceController`), releasing the connection, screenshot
loop, CDP observer, timers, and screen registration. This is the immediate
per-surface teardown hook Future Work still lists generally; it exists today
for agent-browser surfaces.
per-surface teardown hook that [Future](#future) still lists generally; it
exists today for agent-browser surfaces.
- Iframe proxy grants are currently reclaimed by the proxy idle sweep, not by an
immediate per-surface teardown hook.

Expand All @@ -112,6 +112,10 @@ Header contract:
HTML title is tooltip/secondary state.
- Clicking the URL opens an inline editor. `normalizeNavUrl` keeps explicit
schemes, uses `http://` for bare loopback hosts, and `https://` otherwise.
For an iframe-rendered pane a bare remote hostname therefore resolves to
`https://`, which the proxy cannot instrument — the pane shows the scheme
error with its `dor ab` hint. This is intended: remote sites are steered to
the agent-browser renderer rather than silently proxied over plain HTTP.
- Back, forward, and reload are always enabled. Agent-browser sends native
`back` / `forward` / `reload`; iframe uses parent-side history and re-resolves
the proxy on reload/back/forward.
Expand Down Expand Up @@ -271,7 +275,9 @@ screenshot through the host's `agentBrowserScreenshot`, and draws that to canvas
with latest-only backpressure. A capture whose bytes are identical to the last
displayed frame (a static page the daemon keeps re-pulsing) costs no decode or
draw; a re-attach bumps a draw generation so a fresh blank canvas still repaints.
If the host cannot screenshot, it falls back to the stream frame path.
A host without `agentBrowserScreenshot` renders only the placeholder — stream
frame bytes are discarded by design (the connection reduces every frame message
to a `frame-pulse`), so there is no frame-drawing fallback path.

The high-rate `[ab-panel]`/`[agent-browser]` stream and screenshot console
diagnostics sit behind the `dormouse.flags.abDebugLogs` localStorage flag, read
Expand Down Expand Up @@ -493,7 +499,17 @@ Source of truth: `lib/src/lib/platform/types.ts`,
`standalone/src/tauri-adapter.ts`, `standalone/src-tauri/src/lib.rs`,
`standalone/sidecar/main.js`.

## Future Work
## Maintainer checklist

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.
- 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.

## Future

- Stable agent-browser profile/state persistence so pop-out preserves logins,
cookies, tabs, DOM state, and scroll.
Expand Down
30 changes: 20 additions & 10 deletions docs/specs/dor-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,18 @@ Invariants:
`surface:1`, `pane:2`.
- List output defaults to refs; commands that list handles accept
`--id-format refs|uuids|both`.
- Workspace/window refs are defined now that Dormouse supports multiple
Workspaces: `workspace:<n>` (and `workspace:<name>` when exactly one Workspace
matches) and `window:<n>` select a container. A `--workspace` target flag and
`dor workspace` management commands (list / new / rename / close / switch) are
the next handles to expose; like every other command they ship with their
snapshot-tested help and the control methods that back them, not ahead of them.
- Reserved: `workspace:<n>` (and `workspace:<name>` when exactly one Workspace
matches) and `window:<n>` select a container. The ref grammar is reserved now
so surface/pane refs never collide with it; the flag and commands that consume
it are staged — see [Future](#future).

## Current Implemented Commands

Implemented commands call private `surface.*` control methods. `surface.list`
derives its response from current Dockview panels plus terminal state/activity
snapshots where available, then returns `workspace:1` and `window:1`. Once
`surface.list` is made Workspace-aware it tags each surface with the real
`workspace:<n>` / `window:<n>` membership defined above; until then it reports the
single active Workspace.
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`
Expand Down Expand Up @@ -240,3 +237,16 @@ from `command-detail`.
unified `browser` surface, see [dor-browser.md](dor-browser.md)
- `dor list-panes` [impl](../../dor/src/commands/list-panes.ts) [docs](../../dor/test/snapshots/help/list-panes.md)
- `dor list-pane-surfaces` [impl](../../dor/src/commands/list-pane-surfaces.ts) [docs](../../dor/test/snapshots/help/list-pane-surfaces.md)

## Future

- **Workspace handles and commands** — a `--workspace` target flag and `dor
workspace` management commands (list / new / rename / close / switch)
consuming the reserved `workspace:<n|name>` / `window:<n>` ref grammar in the
handle model above. Like every other command they ship with their
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.
Loading
Loading