Spec suite overhaul: lifecycle convention, verification sweep, and pairing presence#209
Merged
Conversation
AGENTS.md gains a "Spec lifecycle" section codifying how dream-first specs stay accurate: everything above a spec's final "## Future" section is present-tense truth about the code; unbuilt design lives below it, cut down into named scopes defined in exactly one spec; dream-driven constraints stay in the body marked "Reserved:"; and implementing a staged item includes promoting its text above the fold. Bring all 20 specs into compliance: - Consolidate the workspace rollout ledger into layout.md ## Future (Scope: workspaces-rollout); glossary, alert, transport, and vscode now link to it instead of restating stages. - Restructure remote-api.md so the body matches the shipped wire contract (server-lib-common/src/remote/wire.ts): terminal-only DirectoryEntry, no hello capabilities field. Browser surfaces, VR wall, graded grants, WebRTC, QoS hardening, and the "tethering to <device>" display (verified unimplemented) move under ## Future. "protocol-v1" is now the named scope for the shipped protocol, dissolving the v1 ambiguity with server.md. - Promote completed plans: vscode.md "Dream architecture" becomes ## Future (dream commands renamed to command/passthrough), server.md's finished five-slice build order becomes a present-tense Testing section, pocket-app.md phase 1 (implemented as PocketWall.tsx) is struck. - Normalize the remote suite to single-H1 headings; move the WebAuthn PRF section to ## Future; fold the challenge-reuse property into remote-security-model.md Connection Establishment. - Move remaining forward-looking passages below the fold: dor-cli workspace handles (ref grammar stays as Reserved:), terminal-escapes fish integration, tutorial follow-up scenarios, shortcuts workspace bindings; rename dor-browser/mobile-ui/mouse-and-clipboard future sections to ## Future and make them last. - Accuracy fixes verified against code: layout.md shortcut table lists m/d for minimize (handle-pane-shortcuts.ts), stale "Tether prototype" name becomes Pocket, server.md tethering-display claim corrected and its two fake hosts disambiguated, missing glossary callouts added (theme, tutorial, server), AGENTS.md index drift fixed (tutorial storage keys, tethering, dream-architecture phrasing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Index all 20 specs (shortcuts, auto-update, mobile-ui, and deploy were missing — mobile-ui is the largest spec in the repo and defines the components pocket-app.md ships on). Slim every entry to what-it-owns plus load-bearing touch points; each spec's own Files/Code Map section is now the exhaustive file→spec mapping. alert.md, terminal-state.md, and mouse-and-clipboard.md gain Files sections so the detail dropped from the index keeps a home. Merge the duplicated Architecture and Project Structure sections into one Architecture list, adding the previously unlisted dor/ and dor-lib-common/ packages with a one-line disambiguation of the two *-lib-common names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each shared rule now has one owning spec; the others defer with a link: - Keystroke fallback / OSC promotion: terminal-state.md is the single owner, with the reducer restructured into OSC-driven events vs a dedicated Keystroke fallback subsection (shape learning, submit parsing, reconnect seeding, swap safety, synthesized transitions, per-pane retirement) plus a compact flow sketch. terminal-escapes.md's fallback section slims to the injection-failure context and a pointer. - <idle>/<unnamed> title validation: transport.md owns the setTerminalUserTitle rules; layout.md's inline-rename section now defers instead of re-explaining the sentinel. - Theme resolver null-default rules: theme.md owns them; vscode.md's restatement of the list.inactiveSelectionForeground case becomes a pointer. Also: move alert.md's cfg.alert source-of-truth anchor up to the Attention section where T_USER_ATTENTION is first discussed, and footnote that BEL is not an OSC in terminal-escapes.md's Supported OSCs table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add a Modes section to the glossary: passthrough and command are the canonical names (layout.md owns switching behavior); ban the "terminal mode" / "normal mode" / "navigation mode" aliases. shortcuts.md drops its alias, gains a banner marking it a derived quick-reference (layout.md and mouse-and-clipboard.md own the behavior). - Add the canonical mapping table for the three surface-kind enums (persisted surfaceType / renderMode / legacy CLI SurfaceType), previously assembled by the reader from three specs; the Naming conventions bullet defers to it. - Soften the Liskov contract to what the code enforces today (documented preconditions, caller checks the layer) and stage uniform typed-error enforcement under a new glossary ## Future — no marker for it exists in the registry code. - Add "tether" to the overloaded-terms table (remote size-authority sense only) and point at remote-api.md for remote-only vocabulary (Viewer). - layout.md: note that keyboards without a right Meta key rely on the Shift gesture (fact), and stage a platform-aware baseboard hint under ## Future (the hint is a static LCmd → RCmd string today). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
remote-api.md: the staged VR subscription replicated "the wall" with a snapshot that nested Workspaces above Windows — inverting the glossary's Window ⊃ Workspace ⊃ Pane ⊃ Surface containment, and colliding with the glossary's Wall (renderer of exactly one Workspace). Rename the concept to the Window it actually replicates: window.watch / window.lease / WindowSnapshot, with workspaces containing panes directly (a session connects to one Host, hence one Window) plus an activeWorkspaceRef. The reserved hello capability and prose references follow. server.md: all five build slices shipped and selfhost is a permanently supported mode, so drop the POC framing — retitle to "Server (selfhost)", "Guardrails", "Running it". The guardrails themselves are unchanged; they describe real current limits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The spec was the original design pitch: hedged "recommended implementation" language for code that now exists, no source-of-truth pointers, and message flows duplicating server.md's sequence diagrams. Now: - Every implemented claim is anchored: passkey verification (passkey.ts), device keys (deviceKey.ts + the IndexedDB store in lib/src/remote/client/device-key.ts, including the DEVICE_AUTH_DOMAIN domain separation), the real HostAclRecord schema and pair-matching semantics (acl.ts), pairing tickets and TTL (pairing.ts), and authorizeConnection + HostChallengeIssuer (connection.ts, challenge.ts). - The pairing and connect wire sequences defer to server.md's diagrams; this spec states what they must establish. - Unimplemented guidance moves below the fold: navigator.storage.persist() and iOS install-before-pairing gating (verified absent from the code) are staged under ## Future alongside WebAuthn PRF, with the durability facts themselves staying in the body. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- tutorial.md's mouse/clipboard coverage table referenced mouse-and-clipboard.md by bare section numbers, so a renumbering there would silently break it. Every § ref is now a markdown link to the named heading anchor. This also surfaced one already-stale ref: "§8.7 right-click paste" pointed at what is now Drag-to-Paste; the prose now points at the Future section that actually owns both deferrals. - layout.md and dor-browser.md gain Maintainer checklists (the theme.md pattern): cross-spec ownership rules for header elements and shortcut tables, persistence-migration routing, the design-token rule, the clip-path animation constraint; renderMode canonicity, renderer:'always', the AGENT_BROWSER_ALLOWED_SUBCOMMANDS security boundary, spawnAndCapture, and closed-session-mark rules. - dor-browser.md: document the intended UX of normalizeNavUrl defaulting bare remote hostnames to https:// in an iframe pane (scheme error + dor ab hint steers remote sites to the agent-browser renderer instead of proxying them over plain HTTP). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mobile-ui.md was titled "Website Prototype Spec" while defining the durable behavior of MobileTerminalUi/MobileWall — the components the real Pocket app ships on. Replace it with mobile-terminal-ui.md, a component spec: core layout and keyboard reserve, touch modes, the radial gesture menu, input modes, Type-mode rules, touch/keyboard-suppression invariants, Files, and Future (Recent/Draft reserves are explicitly placeholder-only). Prototype goals, v0 scope, and success criteria are retired to git history via a short History note; the fake-shell behavior the playground needs moves into tutorial.md, which owns the page wiring. Inbound references updated: the AGENTS.md index entry and the blur-repeat comment in MobileTerminalUi.tsx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vscode.md gave the VS Code host a single home; the standalone host's details were scattered across six specs. standalone.md now owns the host layer — verified against the code: the thin-Rust architecture (lib.rs supervises the sidecar and bridges invoke commands to JSON-lines stdio; all real logic runs in the Node sidecar sharing the same lib/src/host modules the VS Code host runs, bundled by build-sidecar-proxy.mjs), the main.tsx boot sequence (init-before-resume ordering), the sidecar shutdown ordering (closePoppedOut race → control socket → killAll) and the parent-PID watchdog that keeps a force-killed host from orphaning a sidecar holding ConPTY files open, the AppBar, localStorage persistence through window-persistence, file drop, logging paths, and the build/dev workflow — including the vite-only beforeDevCommand caveat (sidecar/host-code edits need a manual re-stage + restart). Behavior owned elsewhere stays elsewhere: the spec links to transport, dor-cli, dor-browser, auto-update, theme, layout, and deploy rather than restating them. Indexed in AGENTS.md between vscode.md and auto-update.md; also fixes the pocket-app entry's stale "mobile-ui composition" phrase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reducer restructure left the CWD precedence rules visually nested under the new Keystroke fallback subheading; promote them to a sibling subsection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CLAUDE.md is a one-line @AGENTS.md import so the spec index and lifecycle conventions are in context from the first turn — until now they only applied when a session was explicitly pointed at AGENTS.md. The Design section now points at PRODUCT.md (users, brand personality, aesthetic anti-references) alongside DESIGN.md, which quotes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ription
theme.md's dynamic-palette coverage knew only door-bg/door-fg/focus-ring;
the code also computes --color-alarm-vs-{header-active,header-inactive,door}
(dynamic-palette.ts). Now specified: the pick rule (plain white or black by
OKLab lightness of the surface the ringing bell sits on, via
pickAlarmColor), the ansiYellow body-level baseline that renders before the
first dynamic pass, the one-pass-behind subtlety for alarm-vs-door (it
derives from door-bg computed by the same pass; the MutationObserver
re-fires on the pass's own body.style write), Storybook decorator coverage,
and pickAlarmColor in the maintainer checklist's no-forking rule.
Two stale claims corrected along the way: theme.md named text-warning as the
ringing-alert exception, a class that no longer exists anywhere in lib/src
(it's text-alarm-vs-*); DESIGN.md described the alarm tint as "per-surface
OKLCH hue-rotation", but pickAlarmColor is a white-or-black lightness pick.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dependency-free node script checks the four mechanically-checkable spec conventions: every docs/specs/*.md indexed in AGENTS.md, ## Future last in its file (the fold), every relative markdown link and #anchor resolving, and every backticked repo path existing on disk (so Source of truth pointers rot into test failures instead of silently). Build/generated outputs that don't exist in a clean checkout are skip-listed. Wired as `pnpm lint:specs` and as the first step of the root `pnpm test`, which CI already runs. AGENTS.md's Spec lifecycle section names the enforcement. Verified both ways: the current suite passes clean, and a synthetic violating spec trips all four check categories. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # docs/specs/pocket-app.md # docs/specs/remote-api.md
One verification agent per spec (21 total) checked every normative body claim against the code; 5 specs came back fully clean (glossary, transport, pocket-app, dor-cli, terminal-state-modulo-wording). Confirmed drift fixed: - shortcuts.md + layout.md: the dual-tap gesture only ENTERS command mode (exit-only from passthrough), never toggles; untouched panes/doors kill without the confirm prompt; the swap chord accepts either modifier on every platform; the passthrough selection ring is 1px with no glow; doors get no overlay inflation; the baseboard hint is already platform-aware (the Future item staging that work is deleted — it shipped); the 440ms auto-spawn delay applies to the minimize path only. - mouse-and-clipboard.md: Ctrl+V is intercepted for paste on every platform including macOS (deliberately shadowing readline quoted-insert) — the macOS keybinding table, §8.3 title, and the IS_MAC claim now say so; the selection outline color chain is focus-ring → active-header → cornflower fallback (never terminal fg/selection bg); bracketed paste reads the store mirror; image paste writes <uuid>-clipboard.png; drag-to-paste is documented as inert (dragDropEnabled=false for HTML5 pane dragging). - remote-api.md + server.md: terminal.resize / terminal.semantic are reserved wire shapes with no emitter — the Host sends only terminal.data and terminal.closed; semantic state reaches clients via directory.snapshot. server.md also fixes: ACL/enrollment persist in webview localStorage (not platform saveState; also fixed in remote-security-model.md), scripts/fake-host.mjs serves the full FakeHost echo terminals (not hello-only), the Testing section's harness/coverage claims, setup/begin is password-gated only, and the pairing credential check is account-level. - remote-security-model.md: "revocation propagation" moved from Server responsibilities to ## Future (no callers, no wire frame). - tutorial.md: Ctrl+C exits the tut runner outright; bundled themes are a VS Code set (not "GitHub variants"); theme-delete falls back to the page's defaultThemeId; base fake shell only handles Up/Down. - standalone.md: the AppBar event is dormouse:new-terminal (hyphenated CustomEvent, not the VS Code message name); file drop marked inert; clipboard-ops.js role corrected (Windows agent-browser writes still use it); csp.mjs runs only when DORMOUSE_REMOTE_CONNECT_SRC is set. - dor-browser.md: no stream-frame fallback exists — hosts without agentBrowserScreenshot show the placeholder. - vscode.md/theme.md/terminal-escapes.md/auto-update.md/deploy.md/ mobile-terminal-ui.md/terminal-state.md: observer attribute filters stated exactly; terminal-theme API is not behind the registry facade; debugger doesn't report alarm picks; LC_TERMINAL set unconditionally; failure-marker launches skip the update check; the notice is 12px; the secrets table matches the script; Type-mode focus is synchronous + rAF retries; process-CWD precedence is source-based. Also fixes three stale code comments the sweep surfaced: the VS Code parser's colorProvider JSDoc, the agentBrowserScreenshot fallback claim in platform/types.ts, and IFRAME_SHIM's "posts two things" (it posts four). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The verification sweep found these two host→client events defined in wire.ts and handled (dead) in pocket-client, but emitted by nothing. Events are dispatched by name, so reserving names buys no compatibility protection — a future event lands additively and old clients ignore unknown names. Remove REMOTE_EVENTS.terminalResize/terminalSemantic, the TerminalResizeEvent interface, pocket-client's dead branch and its unused TerminalHandlers.onResize hook. The client→host terminal.resize METHOD (size authority) is untouched and live. remote-api.md's body now shows the two-event stream as the whole of v1; the event definitions move fully below the fold into Future item 5 (tethering display), whose client half they are. server.md's protocol-v1 summary follows. Verified: server-lib-common build + tests, lib remote tests (67/67), server tests (72/72) all pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Bell click after an attention-dismissed ring now opens the dialog even
from WATCHING_DISABLED (reachable because protocol/command-exit rings need
no WATCHING): both AlertManager.dismissOrToggleAlert and the
session-activity-store mirror consult attentionDismissedRing in that
branch, matching alert.md's stated rule. Regression test added.
- terminal-theme.ts now observes class + style on BOTH body and html,
matching the resolver's observer, so a theme signaled via an <html> class
mutation refreshes the terminal palette. vscode.md/theme.md updated to the
new symmetric truth.
- fake-host.mjs harness sends grants { layout: false }, mirroring the
shipped Host and the protocol-v1 invariant.
- BrowserSidecarHost: remove the never-populated pending map and its
requestId-correlation branch — request/response correlation happens over
the /invoke HTTP round-trip, and every SSE event fans out to handlers
(which is what the dead branch already reduced to).
- ITERM2_COMPAT_VERSION gains keep-in-sync mirror comments on both
definition sites (the OPEN_PORT_TIMEOUT_MS house pattern).
- OSC 99: alert.md clarified rather than code changed — kitty's done-flag
semantics apply regardless of the final chunk's payload type, so a
d=1 icon/buttons chunk completing a pending accumulated notification is
correct; the management payload itself still contributes no content.
Verified: full lib suite 844/844, server 72/72, standalone tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes the sweep's remaining security finding: pairing.ts's contract asked for a fresh passkey assertion before ceremony.begin(), but nothing verified one — pairing rode the hours-long signed-in session. The model, per discussion: presence for pairing is server-attested plus Host-approved. - Session gains lastVerifiedPresence, set at sign-in and refreshed by any server-verified assertion — /api/reauth/finish and a verified connect2 handshake — so "sign in then Pair" and "connect to A, pair B" both cost no extra prompt. - checkPair rejects when the stamp is older than PAIRING_PRESENCE_WINDOW_MS (30s, named constant in pairing.ts) with the stale-presence error code, before anything reaches the Host. The window gates the request, not the approver's deliberation (that stays the 5-minute pairing-ticket TTL). - New /api/reauth/begin + /api/reauth/finish (session-token auth) re-assert presence on the SAME session and relay socket — sign-in's verification, extracted into a shared verifyFreshAssertion, without re-minting the token. - PocketClient.pair() maps stale-presence to one WebAuthn prompt and a single retry of the identical request. - The relay threads the session into the gate (ClientConn.session / PresenceSession); pairing.ts's integration contract now documents the server-attested + human-approval model, as do remote-security-model.md (Pairing Ceremony, with the server-compromise rationale) and server.md. Tests: end-to-end stale → reauth → approved through the real relay with a manual clock; client-side retry sends the identical request with bearer-authorized reauth calls; connect2 presence refresh asserted. Full workspace suite green (root pnpm test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying mouseterm with
|
| Latest commit: |
bda2824
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e5408235.mouseterm.pages.dev |
| Branch Preview URL: | https://spec-refinement.mouseterm.pages.dev |
dormouse-bot
approved these changes
Jul 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A ground-up accuracy pass on the spec suite, plus the code fixes it surfaced. Three arcs:
1. Spec lifecycle convention
Specs here are written dream-first, cut to the smallest shippable slice, and keep the remainder for planning. AGENTS.md now codifies how that works without eroding accuracy: everything above a spec's final
## Futuresection is present-tense truth about the code; unbuilt design lives below it as named scopes (e.g. workspaces-rollout, defined once in layout.md and linked from everywhere else); dream-driven constraints stay in the body markedReserved:; promoting text above the fold is part of a feature's definition of done. All specs comply, andscripts/spec-lint.mjs(first step of rootpnpm test) enforces the mechanical parts: complete index, Future-last, resolving links/anchors, and every backticked repo path existing on disk.2. Index + structural rework
dor/+dor-lib-common/listed.CLAUDE.mdimports AGENTS.md so every session starts with the rules.Window ⊃ Workspace ⊃ Pane); server.md de-POC'd; glossary owns mode names and the surface-kind enum mapping.3. Spec-vs-code verification sweep + fixes
21 agents (one per spec) checked every normative claim against the code. ~30 drifted claims fixed across 14 specs — highlights: the dual-tap gesture only enters command mode; macOS Ctrl+V is intercepted for paste;
terminal.resize/terminal.semanticwere never emitted (now deleted from the wire contract and staged in Future); drag-to-paste is inert (dragDropEnabled: false); ACL/enrollment persist in localStorage, not platformsaveState. Code-side findings fixed: the bell-dialogattentionDismissedRingrule inWATCHING_DISABLED(with regression test), theme-observer class+style symmetry on both roots, harnesslayoutgrant, deadBrowserSidecarHost.pendingmap,ITERM2_COMPAT_VERSIONsync comments, three stale code comments.Pairing presence (security): the sweep found pairing rode the hours-long session despite pairing.ts's contract demanding fresh presence. Now server-attested: sessions carry
lastVerifiedPresence(refreshed by sign-in, re-auth, and verified connects);checkPairrejects requests older thanPAIRING_PRESENCE_WINDOW_MS(30s) withstale-presence; new/api/reauth/*endpoints let Pocket re-assert with one WebAuthn prompt on the same session/socket and retry automatically. The Host's human approval modal remains the backstop, which is what bounds a server-compromise forgery of the stamp.Verification
pnpm testgreen (spec-lint + lib 844+ / server 73 / server-lib-common / standalone / dor / website).🤖 Generated with Claude Code