Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
69ae1f2
docs(specs): sketch the remote surface API for phone and VR clients
nedtwigg Jul 2, 2026
671d4df
docs(specs): remote API v1 decisions — WebSocket relay, selfhost-firs…
nedtwigg Jul 2, 2026
15b57db
docs(specs): terminal attach = resize, last-attach-wins sizing, seman…
nedtwigg Jul 2, 2026
a6f5b72
docs(specs): shrink remote API v1 to the minimal phone path
nedtwigg Jul 2, 2026
a76b2bf
docs(specs): selfhost server POC spec — setup password to phone termi…
nedtwigg Jul 2, 2026
a35b518
feat(server-lib-common): shared wire contract for the selfhost POC
nedtwigg Jul 2, 2026
a7d835c
feat(server): selfhost accounts and passkey auth (slice 1)
nedtwigg Jul 2, 2026
56f86ac
feat(server): host enrollment and WebSocket relay (slice 2)
nedtwigg Jul 2, 2026
bd83bca
feat(server): security handshake over the relay (slice 3)
nedtwigg Jul 2, 2026
e15d39a
feat(lib): remote host module — enrollment, pairing approval, termina…
nedtwigg Jul 2, 2026
b49ec26
feat(lib,server): Pocket web app and static serving (slice 5)
nedtwigg Jul 2, 2026
412bd59
docs(specs): running-the-POC instructions after all five slices landed
nedtwigg Jul 2, 2026
de385da
fix(server): CORS on /api/* so the standalone webview can enroll
nedtwigg Jul 2, 2026
b4d5785
docs(specs): pocket app architecture — the remote session is a platfo…
nedtwigg Jul 2, 2026
19895fd
refactor(lib): move pocket protocol modules to remote/client and add …
nedtwigg Jul 2, 2026
8bc86bd
feat(lib): Pocket app on MobileTerminalUi + RemotePtyAdapter (phase 1b)
nedtwigg Jul 2, 2026
1efb176
chore(server): gitignore the runtime state dir
nedtwigg Jul 2, 2026
5c2220b
refactor(remote,server): consolidate duplicated helpers behind shared…
nedtwigg Jul 3, 2026
c9e4ecd
docs: index the remote-control specs and packages in AGENTS.md
nedtwigg Jul 3, 2026
f827026
docs(specs): add the glossary callout to remote-api.md
nedtwigg Jul 3, 2026
21c98e1
fix(standalone): resolve server-lib-common from source in tsc --noEmit
nedtwigg Jul 3, 2026
9078aeb
fix(lib): alias server-lib-common to source in Storybook build
nedtwigg Jul 3, 2026
5802d8d
Merge remote-tracking branch 'origin/main' into security-model
nedtwigg Jul 3, 2026
0c51821
fix(website): alias server-lib-common to source in the Vite build
nedtwigg Jul 3, 2026
81a8692
fix(server): ignore frames from displaced host sockets
nedtwigg Jul 3, 2026
faad4ad
fix(lib): resolve server-lib-common from source in the Pocket build
nedtwigg Jul 3, 2026
19ef5d0
fix(lib,server): remote session lifecycle — stale detach, resident pa…
nedtwigg Jul 3, 2026
7cb2ca4
Build server-lib-common before dormouse-lib
nedtwigg Jul 3, 2026
361ae51
Subscribe before remote attach repaint
nedtwigg Jul 3, 2026
fd18629
Drop stale relay host replies
nedtwigg Jul 3, 2026
a6eec22
Notify previous relay host on rebind
nedtwigg Jul 3, 2026
7351192
Resolve standalone server-lib-common source
nedtwigg Jul 3, 2026
32dedb7
test(lib): Storybook stories for the remote pairing modal and Pocket …
nedtwigg Jul 3, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules/
dist/
dor/src/generated-version.ts
lib/dist/
lib/dist-pocket/
*.tsbuildinfo

# Vite cache
Expand Down
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pnpm build # build lib, vscode extension, and website
- **standalone/** — Tauri desktop app with Node.js sidecar for native PTY via node-pty
- **vscode-ext/** — VS Code extension wrapping the lib in a webview with native PTY backend
- **website/** — Marketing website bundling part of the lib as an interactive demo
- **server/** — Hono coordinating server for remote control: selfhost accounts + passkey auth, WebSocket relay between Pocket clients and Hosts, serves the built Pocket app
- **server-lib-common/** — Runtime-agnostic security primitives + wire contract shared by `server`, the Host module in `lib`, and the Pocket app

## Project Structure

Expand All @@ -25,6 +27,9 @@ pnpm build # build lib, vscode extension, and website
- `standalone/src-tauri/` — Rust backend that bridges webview ↔ Node.js sidecar
- `vscode-ext/` — VS Code extension (esbuild, node-pty via forked child process)
- `website/` — Marketing site (Vite, uses FakePtyAdapter for demo)
- `server/` — Selfhost server (Hono; accounts in local JSON files, no database)
- `server-lib-common/` — Shared security + wire contract (bare ES2022 — no DOM or Node types)
- `lib/src/remote/` — Remote control: `host/` (laptop side), `client/` (phone-side protocol + `RemotePtyAdapter`), `pocket-app/` (Pocket shell), `ws.ts` (shared socket surface)

## Specs

Expand All @@ -42,6 +47,10 @@ The primary job of a spec is to be an accurate reference for the current state o
- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane layout, interactive `tut` TUI runner with three sections (keyboard navigation, alerts/TODOs, copy/paste), per-item detection wired to `WallEvent` / activity store / mouse-selection store, single-key `dormouse-tut-v3` localStorage scheme, theme picker, and FakePtyAdapter extensions (`sendOutput`, `pumpActivity`, `setInputHandler`). Read this when touching: `website/src/pages/PlaygroundDesktop.tsx`, `website/src/pages/PocketPlayground.tsx`, `website/src/components/PocketTerminalExperience.tsx`, `website/src/lib/playground-routing.ts`, the `website/src/pages/Playground.tsx` and `website/src/pages/Pocket.tsx` redirect dispatchers, `website/src/lib/tut-runner.ts`, `website/src/lib/tut-detector.ts`, `website/src/lib/tutorial-state.ts`, `website/src/lib/tut-items.ts`, `website/src/lib/tutorial-shell.ts`, `lib/src/components/ThemePicker.tsx`, `lib/src/lib/themes/`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), the `WallEvent` union, or the `onApiReady`/`onEvent`/`initialPaneIds` props on Wall.
- **`docs/specs/theme.md`** — Theme system: two-layer CSS variable strategy, theme data model, conversion pipeline, bundled themes, localStorage store, shared ThemePicker component, standalone AppBar picker, runtime OpenVSX installer. Read this when touching: `lib/src/lib/themes/`, `lib/src/components/ThemePicker.tsx`, `lib/src/theme.css`, `lib/scripts/bundle-themes.mjs`, `standalone/src/AppBar.tsx` (theme picker), `standalone/src/main.tsx` (theme restore), or `website/src/components/SiteHeader.tsx` (themeAware mode).
- **`docs/specs/mouse-and-clipboard.md`** — Terminal-owned text selection, copy (Raw / Rewrapped), bracketed paste, smart URL/path extension, mouse-reporting override UI (icon + banner), and the state matrix for which layer owns mouse events. Read this when touching: `lib/src/lib/mouse-selection.ts`, `lib/src/lib/mouse-mode-observer.ts`, `lib/src/lib/clipboard.ts`, `lib/src/lib/rewrap.ts`, `lib/src/lib/selection-text.ts`, `lib/src/lib/smart-token.ts`, `lib/src/components/SelectionOverlay.tsx`, `lib/src/components/SelectionPopup.tsx`, the mouse icon / override banner / Cmd+C-V handling in `lib/src/components/Wall.tsx`, or the parser hooks + mouse listeners in `lib/src/lib/terminal-registry.ts`.
- **`docs/specs/remote-security-model.md`** — The trust model for remote control, built on two independent primitives: passkeys prove fresh user presence (user credentials, never device identities — they sync), and per-browser non-extractable device keys prove long-lived Client identity. The Host's local ACL authorizes the *pair* of a passkey credential and a device key; the pairing ceremony (local approval on the Host) is the only path into it, and the Host — never the Server — makes the final access decision. Covers the connection requirements (all layers must agree), storage durability (iOS PWA guidance), and device-key-loss recovery. Read this first for anything remote; the other three remote specs build on it. Read this when touching: `server-lib-common/src/security/` (deviceKey, challenge, passkey, acl, pairing, connection), `server/src/handshake.ts`, `lib/src/remote/host/{acl,pairing-approval,remote-host}.ts`, or `lib/src/remote/client/{device-key,webauthn}.ts`.
- **`docs/specs/remote-api.md`** — The protocol a Client speaks after `authorizeConnection`: one protocol at two consumption depths (phone = directory picker + one attached surface; VR = full wall, future). Defines the v1 scope (terminal-only decisions live in server.md), the directory (snapshot-only, `ringing`/`hasTODO`), attach-is-the-resize (the SIGWINCH repaint is the screen transfer — no snapshot format), last-attach-wins size authority with the "tethering to \<device\>" display, and the staged future work (in-flight replay, semantic command scrollback, thumbnails, grants, VR wall, WebRTC). Read this when touching: `server-lib-common/src/remote/wire.ts` (the fixed wire contract), `lib/src/remote/host/remote-api.ts`, `lib/src/remote/client/{pocket-client,remote-adapter}.ts`, or `server/test/harness/fake-host.mjs`.
- **`docs/specs/server.md`** — The selfhost coordinating server: env config (`DORMOUSE_SETUP_PASSWORD`, `DORMOUSE_ORIGIN`, …), two-JSON-file state (no database), "WebAuthn without a WebAuthn library" (registration via `response.getPublicKey()`, assertions via the same `verifyPasskeyAssertion` the Host uses), the HTTP API, the relay frame flow (pairing and connect sequence diagrams — one host challenge feeds both the passkey assertion and the device signature, so connecting costs one biometric prompt), the Host and Pocket side responsibilities, and "Running the POC" instructions. Read this when touching: `server/src/` (app, relay, handshake, state), `lib/src/remote/host/enrollment.ts`, or the `dev:pocket-server` flow.
- **`docs/specs/pocket-app.md`** — Pocket app architecture: the remote session is a `PlatformAdapter` (`RemotePtyAdapter` maps the PTY core onto remote-api one-to-one), so Pocket is auth screens + `MobileTerminalUi`/`MobileWall` — the same composition the website playground runs on `FakePtyAdapter`. Covers the `lib/src/remote/` module layout and the same-origin deployment rule (WebAuthn origin binding + Chrome PNA: selfhost server serves the bundle now; CloudFlare static + routed `/api`+`/ws` for SaaS later). Read this when touching: `lib/src/remote/client/remote-adapter.ts`, `lib/src/remote/pocket-app/`, `lib/vite.pocket.config.ts`, or the Pocket static serving in `server/src/app.ts`.

When updating code covered by a spec, update the spec to match. When the two specs overlap (e.g. pane header elements appear in both), layout.md documents placement and sizing while alert.md documents behavior and visual states.

Expand Down
86 changes: 86 additions & 0 deletions docs/specs/pocket-app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Pocket App Architecture

How the phone client (Dormouse Pocket) is structured and deployed. Companion
to [remote-api.md](./remote-api.md) (the protocol) and
[server.md](./server.md) (the selfhost server).

# The seam: the remote session is a platform adapter

`lib` renders every Dormouse surface through a `PlatformAdapter`
(`lib/src/lib/platform/types.ts`). The adapter's PTY core — `writePty`,
`resizePty`, `onPtyData`, `onPtyExit`, plus the `requestInit`/`onPtyList`/
`onPtyReplay` resume path built for VS Code webview reloads — maps one-to-one
onto the remote-api v1 terminal protocol:

| PlatformAdapter | remote-api |
| ------------------------ | --------------------------------------- |
| `onPtyList` | `directory.snapshot` |
| attach semantics | `surface.attach` (attach-is-the-resize) |
| `onPtyData` | `terminal.data` |
| `writePty` | `terminal.write` |
| `resizePty` | `terminal.resize` |
| `onPtyExit` | `terminal.closed` |

So the Pocket app is NOT a bespoke terminal UI. It is:

> auth screens + `MobileTerminalUi`/`MobileWall` + **`RemotePtyAdapter`**

— the exact composition the website playground already proves out with
`FakePtyAdapter` (`website/src/components/PocketTerminalExperience.tsx`).
One mobile terminal experience in `lib`, three consumers: the website
playground (fake adapter), the real Pocket (remote adapter), and whatever
comes later. Everything not in the PTY core no-ops or is absent — the
interface is designed for capability degradation.

Adapter-specific extras (the same pattern as `FakePtyAdapter`'s scenario
controls): the concrete `RemotePtyAdapter` exposes `setActivePane(id)` — the
v1 protocol allows one attachment per session, so pane switching is
detach → attach, and the attach repaint (resize) redraws the screen. Badges
for non-attached panes come from `directory.watch` without attaching.

# Module layout

```
lib/src/remote/
client/ the phone side
pocket-client.ts UI-free protocol client (auth, pair, connect, msg)
device-key.ts non-extractable device key in IndexedDB
webauthn.ts navigator.credentials wrappers
remote-adapter.ts RemotePtyAdapter (PlatformAdapter over pocket-client)
host/ the laptop side (enrollment, approval modal, ACL, bridge)
pocket-app/ the app shell: auth views + the mobile wall composition
```

The server (`server/`) stays the only dynamic code: accounts, relay, and
static serving of the built Pocket bundle.

# Deployment: same-origin, always

WebAuthn binds passkeys to the serving origin, and Chrome's Private Network
Access rules are progressively blocking public-site → private-network fetches.
Both point the same way: **the Pocket app is always served same-origin with
its API.** One lib-owned bundle, two deployments:

* **Selfhost (now):** the Node server serves the bundle (`lib/dist-pocket`).
Selfhost auth never depends on dormouse.dev existing.
* **SaaS (later):** CloudFlare serves the static site and routes `/api/*` and
`/ws/*` to the dynamic backend (CloudFlare proxies WebSockets). The same
bundle mounts at the site origin; rpId is the site's. The dynamic surface
is two path prefixes — everything else stays static.

The website keeps its playground and marketing pages fully static in both
worlds and shares all terminal UI through `lib`; it never duplicates Pocket
code.

# Phases

1. **RemotePtyAdapter + real Pocket UI** — move the protocol modules to
`remote/client/`, add the adapter, rebuild the Pocket entry as
auth views + `MobileTerminalUi`/`MobileWall`, delete the bespoke terminal
UI from the POC. Protocol and server untouched. The fake host harness
grows a synthetic directory + echo terminal so the adapter is testable
end-to-end without a real host.
2. **Dedupe the composition** — extract the thin wiring shared by the
website's `PocketTerminalExperience` and the Pocket shell so the two
cannot drift.
3. **CloudFlare routing** — deferred until SaaS; nothing in 1–2 needs rework.
Loading
Loading