← the Atlas

E2E graduation — the browser suite's package-level future

Analysis·seedling·proposed·

A completed read-only review of all 53 feature files / 492 scenarios, classified against the step implementations: ~26% of the browser suite asserts behavior a package API exposes and graduates to padi/kaval/integrations/client-unit tests — mostly by deleting re-proofs the packages already cover — while ~360 scenarios genuinely assert rendered UI and stay. The classification table, the graduation order, the surprises, the #1751 intersection, and the execution done-criteria.

The principle: a test lives at the lowest layer that can honestly assert it. A browser e2e worker pays Xvfb, the built kolu-server binary, a spawned padi, a kaval daemon, and a Chromium context — then 20–60s DOM/hydration polls per scenario (packages/tests/support/hooks.ts/world.ts); a package integration test pays a vitest boot and at most an in-process PTY (packages/kaval/src/inProcessPtyHost.test.ts precedent). Reviewed at ff1c0bd22, grounded in the step implementations (not the Gherkin prose): of 492 scenario executions across 53 feature files, ~130 (~26%) assert behavior a package API exposes and graduate or collapse to a thin smoke; ~360 genuinely assert rendered UI and stay. The headline: graduation is mostly deletion — padi/kaval/solid-markdown/integrations already test most graduatable halves, and several e2e scenarios are 1:1 duplicates of existing package tests (named below). Execution is a separate future PR; this note is the reviewed map.

browser e2e — 53 feature files · 492 scenarios pays per worker: Xvfb · built kolu-server · padi · kaval · Chromium · 20–60s DOM polls ~360 genuinely assert rendered UI — they stay ~130 executions (~26%) graduate down mostly by DELETION — the package test already exists thin smokes PACKAGE TESTS — vitest boot + at most an in-process PTY padi sensors · lifecycle · store kaval OSC · PTY · daemon integrations/* agents · git · anyagent solid-markdown + client/lib units server HTTP routes the principle: a test lives at the lowest layer that can honestly assert it
The move. ~130 of 492 executions graduate from the browser stack to package tests (mostly deletion — the covering test already exists), leaving thin browser smokes; ~360 rendered-UI scenarios stay.

The classification, file by file

File-level: 31 browser-bound · 9 graduatable · 13 mixed.

Feature file Scen. Classification Target package Seam
claude-code.feature 20 GRADUATABLE (keep ~2 smokes) padi + integrations/claude-code terminalWorkspace/sensors.ts startSensors over real temp dirs + in-process PTY
codex.feature 6 GRADUATABLE (keep 1 smoke) padi + integrations/codex same; mock already writes real SQLite (support/agent-mock-codex.ts, node:sqlite)
opencode.feature 7 GRADUATABLE (keep 1 smoke) padi + integrations/opencode same
grok.feature 6 GRADUATABLE (keep 1 smoke) padi + integrations/grok same
recent-agents.feature 6 MIXED integrations/anyagent (largely covered) + padi normalization = agent-cli.test.ts dup; OSC 633;E→MRU = padi sensors commandRun; palette UX stays
activity-alerts.feature 5 BROWSER-BOUND window.__koluSimulateAlert hook, Badging API, switcher glow — pure client wiring
foreground-process.feature 2 GRADUATABLE (keep 1 smoke) kaval (+padi compose) OSC 0/2 title + foregroundPid — ALREADY in kaval/src/ptyHost.test.ts
cwd.feature 2 GRADUATABLE (keep 1 smoke) kaval (+padi compose) OSC 7 cwd — ALREADY in kaval/src/ptyHost.test.ts
kill.feature 8 MIXED padi/kaval PTY-exit removal + stays-gone-after-refresh = kaval inventory deltas + padi/reconcile.test.ts, session.test.ts; dialogs/auto-switch stay
kaval-daemon.feature 5 MIXED padi recycle-preserves-session + fresh-pid = restartLocal.test.ts + kaval socketDaemon.test.ts; degraded/warming UI stays
sleeping-terminals.feature 9 MIXED padi sleep/wake/resume-by-id/malformed-drop/no-orphan = sleepWake.test.ts + reconcile.test.ts; dormant render/dock filters/PTY-replay-in-xterm stay
session-restore.feature 9 MIXED padi restore mechanism = sessionRestore.test.ts + session.test.ts; viewport-centering races, theme, heading UI stay
reconnect.feature 1 BROWSER-BOUND client oRPC ClientRetryPlugin re-subscribe over the page’s own WebSocket
terminal.feature 13 BROWSER-BOUND xterm fit/zoom/focus/client scrollback (server-mirror half already in kaval ptyHost.test.ts)
sub-terminal.feature 17 BROWSER-BOUND split panes/focus routing; restore-re-parent half already in sessionRestore.test.ts
terminal-resize.feature 1 BROWSER-BOUND per-terminal cols sovereignty on mobile — client fit
inherit-size.feature 2 BROWSER-BOUND client resolveReferenceLayout placement
code-tab.feature 101 MIXED (~40 graduate/thin) integrations/git, solid-markdown, padi, solid-browser, client units markdown cluster → render.test.ts/wikilink.test.ts (+ new sanitize.test.ts); filter Outlines → fileSearch.ts/pathReconcile.ts units; status/binary → review.test.ts; listAll/readFile → browse.test.ts; history → createBrowser
file-ref-link.feature 15 MIXED (~9 graduate) client ui/lineRef.test.ts (exists) resolver variants; keep xterm hit-test/touch/re-click canaries
git-context.feature 9 MIXED (mostly graduates) integrations/git resolve.ts + head/cwd watchers: branch, worktree naming, .worktrees collapse
recent-repos.feature 3 MIXED padi MRU tracking graduates; palette picker render stays (1 smoke)
worktree.feature 10 MIXED integrations/git + padi worktree.ts create/remove, collision, validation, shared-blocker; confirm-dialog UI stays
worktree-agent.feature 4 BROWSER-BOUND (trim to 2-3) agent command written into new PTY + sub-palette; MRU/worktree seams graduate via other files
settings.feature 4 BROWSER-BOUND (trim to 1-2) popover + aria-pressed; overlaps preferences
preferences.feature 4 MIXED (3 of 4 graduate) padi confStore write→re-read persistence; keep renderer-swap-live-tile smoke
terminal-intent.feature 8 MIXED (~4 graduate) padi + solid-markdown chrome.setIntent persistence; links-off inert render = render.test.ts dup; editor UX stays
smoke.feature 3 MIXED server /api/health = plain Hono route (packages/server/src/index.ts:542); title + WS-open stay
chrome-memory.feature 2 GRADUATABLE (mostly) padi + kaval memorySampler.ts processMemory cell; kaval surface.system.processMemory
clipboard.feature 1 GRADUATABLE (thin residue) padi upload/scratch.write; paste-event origination is the only browser fact
file-drop.feature 2 GRADUATABLE (thin residue) padi upload gate incl. .mov allowlist — policy ALREADY in servePadi.test.ts (F1)
canvas.feature 48 BROWSER-BOUND tile geometry, WebGL budget, minimap, pan/zoom, maximize
canvas-selection.feature 1 BROWSER-BOUND xterm hit-testing under CSS transform
dock.feature 15 BROWSER-BOUND rendered dock; ranking is pure (terminal-vocab/agentProjection.ts) and separately unit-testable
workspace-switcher.feature 17 BROWSER-BOUND cards/buckets render; bucket taxonomy graduable to terminal-vocab units
command-palette.feature 23 BROWSER-BOUND palette DOM/focus/chords
keyboard-shortcuts.feature 9 BROWSER-BOUND chords, MRU cycling, __wsSent leak-guards
right-panel.feature 20 BROWSER-BOUND panel render; kaval-tui command-string derivation could gain a padi assertion
theme.feature 13 BROWSER-BOUND rendered theme + localStorage
scroll_lock.feature 10 BROWSER-BOUND xterm viewport scrollTop state machine
mobile-*.feature (8 files) 39 BROWSER-BOUND touch gestures, soft keyboard/iOS focus, drawers, swipe
compact-layout.feature 3 BROWSER-BOUND responsive layoutMode render
copy-pane-text.feature 3 BROWSER-BOUND client buffer serialization + navigator.clipboard
osc52-clipboard.feature 2 BROWSER-BOUND xterm ClipboardAddon + clipboard fallback
terminal-screenshot.feature 5 BROWSER-BOUND client canvas capture to clipboard PNG
render_recovery.feature 1 BROWSER-BOUND xterm renderService rAF recovery
welcome.feature 1 BROWSER-BOUND static render (could be a component test)
recordings.feature 1 BROWSER-BOUND marketing capture, no assertion

The graduation order

Speed win × risk, biggest first. The named duplicates are the deletion proof: sleeping-terminals’ malformed-record scenario is a 1:1 dup of sleepWake.test.ts; file-drop’s .mov allowlist duplicates servePadi.test.ts F1; recent-agents’ quote re-quoting duplicates anyagent/src/agent-cli.test.ts; terminal-intent’s inert-markdown duplicates solid-markdown/render.test.ts.

  1. Agent detection → padi sensors + integrations (≈35 retired, ~5 smokes). The suite’s slowest, flakiest band — every scenario pays full-stack boot plus fs-watch nudge polls under HYDRATION_TIMEOUT. The pure derivations are already unit-tested (integrations/*/index.test.ts, screen.test.ts); the one genuinely new surface is the composition — real fs events → watcher → sensors’ TerminalEvent emission against a real PTY. Shape: kaval’s in-process PTY + startSensors + the e2e mock builders ported to a testlib (gitRepo.testlib.ts precedent); the mocks already write real artifacts (real SQLite via node:sqlite) with no Playwright dependency beyond a shell-PID probe an in-process PTY replaces.
  2. Lifecycle redundancy trim → padi/kaval (≈16 retired from kill · kaval-daemon · sleeping-terminals · session-restore · foreground-process · cwd). Near-zero risk: the package tests already exist (sleepWake.test.ts, sessionRestore.test.ts, reconcile.test.ts, restartLocal.test.ts, kaval ptyHost.test.ts:282/327/338 for OSC 7 / OSC 0-2 / foregroundPid) — this is deleting re-proofs and keeping named smokes. No new code.
  3. code-tab markdown + filter + resolver variants (≈25–30). Markdown cluster → solid-markdown (dup-deletion + one new sanitize.test.ts); filter Outlines → fileSearch/pathReconcile units, collapsing the 3×-mode Outlines to one mode; file-ref resolver variants → lineRef.test.ts. Cheap, mechanical.
  4. git facts → integrations/git (≈15): git-context + code-tab status/diff/binary clusters extend review.test.ts/browse.test.ts; new resolve.test.ts assertions and a new worktree.test.ts (create/remove, collision, validation, shared-worktree blocker). Moderate new-test work on well-precedented seams.
  5. The #1751 real-agent lane → integrations/codex + padi real-CLI test, done with W3.4 rather than after — the highest per-scenario speed win in the repo (a real ollama model turn + the browser stack + DOM polls today). See the intersection note below.
  6. Small server stowaways: upload-gate residue (file-drop/clipboard — already covered, keep one smoke), chrome-memory → padi/kaval, /api/health → a server HTTP test, preferences/intent persistence → padi store tests.

The surprises

The #1751 intersection

#1751 adds codex-real.feature: a real codex CLI (pinned 0.130.0 — nixpkgs’ 0.114 predates the threads schema kolu’s detector requires) against a private ollama, asserting thinking→waiting plus artifacts at the real default paths. Every assertion except the DOM readout is server-observable. The natural home once real: an integrations/codex (or padi) package test — codex in a kaval in-process PTY with the ollama env + throwaway HOME, the real watcher over the real ~/.codex, asserting the sensors state stream flips and the artifacts land — keeping the PR’s determinism work intact and removing Xvfb + Chromium + DOM polls from the lane that combines a real model turn with the flakiest wait in the suite. Browser residue: none beyond the mock smoke (the UI indicator is proven there; real-CLI detection is not a UI fact). This is deliberately not redirected into the in-flight PR — it composes with the PR’s own open question (W3.4 decides whether the real lane subsumes the mocks): real lane → package test; mock lane → padi state-machine tests + one browser smoke. The codex nix pin and the linux-only constraint travel with the graduation.

Done-criteria for the execution PR(s)