diff --git a/TESTING_AND_MODIFICATION_GUIDE.md b/TESTING_AND_MODIFICATION_GUIDE.md new file mode 100644 index 000000000..f0957c3fb --- /dev/null +++ b/TESTING_AND_MODIFICATION_GUIDE.md @@ -0,0 +1,239 @@ +# Workspaces stack: testing and modification guide + +For an agent picking up the Workspaces work. Read `AGENTS.md` first (worktree rules, +spec conventions); this guide covers only what is specific to the stack. + +## 1. Where the code is + +The work is a stack of ten branches, each a draft PR based on the one before it. +Every branch has its own worktree as a sibling of the repo: + +| # | PR | Branch | Worktree | What it adds | +|---|---|---|---|---| +| 1 | #614 | `workspaces-window` | `dormouse.workspaces-2/.claude/worktrees/workspaces-window` | Workspace strip, one Wall per Workspace, hidden-Workspace minimize | +| 2 | #615 | `workspaces-persist` | `dormouse.workspaces-2/.claude/worktrees/workspaces-persist` | Per-Workspace persistence, agent recovery | +| 3 | #616 | `workspaces-multiwindow` | `dormouse.workspaces-2/.claude/worktrees/workspaces-multiwindow` | Several windows, routing, quit voting, transfer, drag | +| 4 | #617 | `workspaces-dor` | `dormouse.workspaces-2/.claude/worktrees/workspaces-dor` | `dor workspace` verbs | +| 5 | #618 | `workspaces-harden` | `dormouse.workspaces-harden` | Held events in the transfer gap, drift cleanup | +| 6 | #619 | `workspaces-registry` | `dormouse.workspaces-registry` | Rust registry, stable `workspace:` refs, cross-window routing | +| 7 | #620 | `workspaces-durability` | `dormouse.workspaces-durability` | Transfer moves the snapshot between files on disk | +| 8 | #621 | `workspaces-fidelity` | `dormouse.workspaces-fidelity` | Marks, serialized xterm buffers, pins across a move | +| 9 | #622 | `workspaces-move-verb` | `dormouse.workspaces-move-verb` | `dor workspace move`, `dor list --window`, iframe move gate | +| 10 | #623 | `workspaces-harness` | `dormouse.workspaces-harness` | Harness alert stores through the sidecar | + +**Work on the tip** (`dormouse.workspaces-harness`) for testing and for any tweak, +unless the tweak clearly belongs to an earlier stage and you want it reviewed +there. If you commit on an earlier stage, merge it forward through every later +branch (`git merge --no-ff ` in each worktree, in order), or the PRs +diverge. Push with `git push origin `; the PRs update themselves. + +Never `git switch -c` inside an existing worktree. New branches: +`wt switch --create --base @` from the worktree you are stacking on. + +## 1a. Changing the stack, and landing it + +**Default: put changes in new PRs on top of the tip.** A change to an early +stage has to be merged forward through every later branch; a new PR at the tip +touches nothing behind it, and testing findings usually cut across stages anyway. +Edit an existing PR only when (a) the bug would make that PR wrong to merge on +its own, (b) it answers review feedback on that PR, or (c) the stage's spec text +is untrue. After editing an earlier stage, merge it forward at once, stage by +stage, and run the tip's suites once at the end. + +**Land with merge commits, in order, never squash.** The stack is built on merge +commits, so merging PR N with a merge commit leaves PR N+1's diff exactly its own +and GitHub retargets it to `main` when PR N's branch is deleted; nothing else is +needed. If a PR is squash-merged by mistake, PR N+1 will show PR N's changes +again until `main` is merged into its branch (that merge resolves cleanly, since +both sides carry identical content). The repo's default is a merge commit; keep it. + +Keep every PR a draft until it is actually up for review (Chromatic bills on +ready-for-review). + +## 2. Setup in a worktree + +``` +pnpm install +pnpm --filter dor-lib-common build # dor tests import its dist +pnpm --filter remote-lib-common build # vscode-ext tests import its dist +pnpm --filter dor build # dor CLI tests and help snapshots +``` + +For `cargo test`, the build script needs a self-contained Node 24.18.0 binary and +rejects Homebrew's. Copy the bundled one from the main checkout and point the env +var at that source path, **never at the worktree's own `binaries/` path** (the +build script copies source onto destination and truncates the file to 0 bytes): + +``` +mkdir -p standalone/src-tauri/binaries +cp /Users/ntwigg/projects/dormouse/standalone/src-tauri/binaries/node-aarch64-apple-darwin standalone/src-tauri/binaries/ +cd standalone/src-tauri +DORMOUSE_NODE_BINARY=/Users/ntwigg/projects/dormouse/standalone/src-tauri/binaries/node-aarch64-apple-darwin cargo test +``` + +## 3. Running the app + +- **Tauri (real thing, several windows):** `pnpm dev:standalone` from the repo + root of the worktree. Dev builds use a separate state root + (`/dev`), so dev and installed app never share snapshots. +- **Browser harness (one window only):** inside Dormouse, + `dor ensure -- pnpm innerdogfood`; outside, `pnpm innerdogfood`. It prints the + URL and an `agent-browser` command. The skill + `.claude/skills/debug-standalone-agent-browser/SKILL.md` covers driving it. + The harness simulates **one** window: transfer, tear-out, quit voting, and + cross-window `dor` routing only run in Tauri. + +Opening a second window: drag a Workspace tab out of the strip and release it +outside the window, or from a Dormouse terminal run +`dor workspace move --window new`. + +## 4. Automated tests + +Per package, from the worktree root: + +``` +(cd lib && npx tsc --noEmit -p . && npx vitest run) # ~3000 tests +(cd standalone && npx tsc --noEmit -p . && npx vitest run && node --test scripts/*.test.mjs) +(cd standalone/sidecar && node --test) +(cd dor && node --test) # help snapshots +(cd vscode-ext && npx vitest run) +pnpm lint:specs && node scripts/xterm-lint.mjs +cargo test # see §2 +``` + +Help snapshots: after changing any `dor` help text, rebuild and refresh with +`cd dor && pnpm build && UPDATE_SNAPSHOTS=1 node --test`, then run `node --test` +again and commit `dor/test/snapshots/`. + +Tests that pin the stack's non-obvious rules, by concern: + +| Concern | Tests | +|---|---| +| Hidden-Workspace minimize | `lib/src/components/TerminalPane.test.tsx` ("a hidden Workspace minimizes its terminals") | +| Composition, switching, close race | `lib/src/components/WorkspaceWindow.test.tsx`, `Wall.test.tsx` | +| Strip | `lib/src/components/WorkspaceStrip.test.tsx`, `workspace-strip-drag.test.ts` | +| Quit / close arbitration | `standalone/src/quit.test.ts`, `teardown-arbiter.test.ts`, `window-close.test.ts`; Rust `quit_state` | +| Routing table, arrivals, marking, held events | Rust `routing::tests` | +| Transfer, content, tear-out boot | `standalone/src/workspace-move.test.ts` | +| Cross-window drag and iframe gate | `standalone/src/workspace-drag.test.ts` | +| Registry, stable refs | Rust `workspaces::tests`, `standalone/src/workspace-registry.test.ts`, `lib/src/lib/workspace-store.test.ts` | +| Sidecar marks and since-mark replay | `standalone/sidecar/pty-core.test.js` | +| Pins across a move | `lib/src/lib/notepad/source-link.test.ts`, `notepad-store.test.ts` | +| `dor workspace` verbs, move gate | `lib/src/components/wall/workspace-control.test.ts`, `dor-control-router.test.ts` | +| Disk staging of a transfer | Rust `tests::a_staged_arrival_is_in_the_target_snapshot_until_it_is_handed_back` | + +## 5. Manual test checklist + +Nothing below has been run in the Tauri app yet. Items marked **WKWebView** are +the ones the design depends on and that were validated only in Chromium. + +**Hidden-Workspace minimize (WKWebView)** +- Two Workspaces, three terminals each. Switch away; in Safari Web Inspector the + hidden Workspace's canvases should have lost their WebGL contexts and the + visible one's should be live. +- Switch back: zero PTY resizes at an unchanged grid (watch `pty:resize` in the + sidecar log). Resize the window while hidden: exactly one resize on return. +- 30+ switches with more than 16 terminals total: every pane still shows + `data-renderer="webgl"`, no "too many active WebGL contexts" in the console. +- A full-screen TUI (`btop`) on switch-back: look for a one-frame blank. If seen, + keep the outgoing Wall painted one extra frame; do not abandon detaching. +- Switch into an 8-pane Workspace: watch for jank from N context creations. + +**Transfer and tear-out** +- Move a Workspace with a long-running TUI and 10k+ lines of scrollback to a + second window: scrollback, cursor, and colors intact; output continues with + nothing repeated or lost at the seam. +- A note pinned to scrollback survives the move (click the pin in the target). +- Kill the app mid-drag (after the drop, before the target finishes): on + relaunch the Workspace is in the target window with fresh shells, and not in + the source. +- Tear out a Workspace whose terminals are idle (no output): the new window + appears within a second (the mark round trip is the only wait). +- Drop onto a target and close that target before it adopts: the Workspace + stays in the source, its terminals keep receiving output. + +**Iframe gate** +- A Workspace holding a `dor iframe` Surface: dragging it to another window + raises the typed-letter dialog naming the count; Escape leaves everything as + it was; the letter moves it and the iframe reloads at its URL. +- Same Workspace with only agent-browser Surfaces: no dialog. +- `dor workspace move workspace: --window