The browser app had no tests and no way to run any. package.json shipped dev/build/preview/typecheck; CI installed Node and pnpm and then never ran anything with them — not even typecheck. And its densest logic was module-private, so a runner alone would not have reached it.
Four pure modules extracted from DOM-heavy ones, rather than just exporting in place — attach.ts imports xterm's CSS at module level, which a node test cannot load:
one escapeHtml, replacing copies in catalog.ts, spawn.ts, types.ts and attach.ts (esc)
CatalogStore gains an injectable socket factory, scheduler and refetch, so the reducer and the backoff schedule are drivable without a server. snapshot() now returns a copy — it returned the live object while SessionAdded/Removed/Updated mutated it in place, so listeners could not diff and every single event rebuilt the entire tree.
36 tests across 6 files, including the backoff doubling/cap, reset-on-open, Lagged-triggers-refetch, and that hydrate re-issues ids rather than trusting stored ones.
New web CI job — typecheck, test, build. That is the first CI signal the SPA has ever had.
Two constraints worth naming:
vitest is pinned to 2.x. 4.x requires vite >= 6 and this repo is on vite 5; bumping vite is a separate decision, not something to smuggle into a test-infrastructure PR.
I did not fix the two-uncoordinated-channels problem for session death (the row updates from WS, the pane from ws.onclose, nothing reconciles them). It needs a design call, and this PR is already the infrastructure one.
Verification
pnpm -C web typecheck clean
pnpm -C web test 36 passed (6 files)
pnpm -C web build ✓ built, 32 modules
pnpm -C web install --frozen-lockfile already up to date
cargo test --all 300 passed, 0 failed
Fifth of nine from the architecture review (#40–#43 merged).
The browser app had **no tests and no way to run any**. `package.json` shipped `dev`/`build`/`preview`/`typecheck`; CI installed Node and pnpm and then never ran anything with them — not even `typecheck`. And its densest logic was module-private, so a runner alone would not have reached it.
**Four pure modules extracted** from DOM-heavy ones, rather than just `export`ing in place — `attach.ts` imports xterm's CSS at module level, which a node test cannot load:
| new module | was buried in | what it holds |
|---|---|---|
| `tree.ts` | `catalog.ts` | directory grouping: `buildTree`, `mergeLinear`, `compareNodes` |
| `keys.ts` | `attach.ts` | terminal escape encoding: `toCtrl`, `applyModsToChar`, `cursorSeq` |
| `paths.ts` | `dirAutocomplete.ts` | `splitPath`, `longestCommonPrefix` |
| `html.ts` | four places | one `escapeHtml`, replacing copies in `catalog.ts`, `spawn.ts`, `types.ts` and `attach.ts` (`esc`) |
`CatalogStore` gains an injectable socket factory, scheduler and refetch, so the reducer and the backoff schedule are drivable without a server. `snapshot()` now returns a copy — it returned the live object while `SessionAdded`/`Removed`/`Updated` mutated it in place, so listeners could not diff and every single event rebuilt the entire tree.
36 tests across 6 files, including the backoff doubling/cap, reset-on-open, Lagged-triggers-refetch, and that `hydrate` re-issues ids rather than trusting stored ones.
**New `web` CI job** — typecheck, test, build. That is the first CI signal the SPA has ever had.
Two constraints worth naming:
- vitest is pinned to **2.x**. 4.x requires vite >= 6 and this repo is on vite 5; bumping vite is a separate decision, not something to smuggle into a test-infrastructure PR.
- I did *not* fix the two-uncoordinated-channels problem for session death (the row updates from WS, the pane from `ws.onclose`, nothing reconciles them). It needs a design call, and this PR is already the infrastructure one.
<details>
<summary>Verification</summary>
```
pnpm -C web typecheck clean
pnpm -C web test 36 passed (6 files)
pnpm -C web build ✓ built, 32 modules
pnpm -C web install --frozen-lockfile already up to date
cargo test --all 300 passed, 0 failed
```
</details>
Fifth of nine from the architecture review (#40–#43 merged).
The browser app had no tests and no way to run any: package.json shipped
dev/build/preview/typecheck, and CI installed Node and pnpm but never ran
anything with them. Its densest logic was module-private, so even with a
runner none of it was reachable.
Extracts four pure modules out of DOM-heavy ones — tree.ts (directory
grouping), keys.ts (terminal escape encoding), paths.ts (completion
arithmetic), html.ts (one escaper, replacing four copies). Extraction
rather than `export` because attach.ts imports xterm's CSS at module
level, which a node test cannot load.
CatalogStore takes an injectable socket factory, scheduler and refetch, so
the reducer and the backoff schedule run without a server. snapshot() now
returns a copy: it returned the live object while events mutated it in
place, so listeners could not diff and every event rebuilt the whole tree.
36 tests across 6 files, and a `web` CI job running typecheck, test and
build. vitest is pinned to 2.x — 4.x requires vite >= 6 and this is vite 5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The browser app had no tests and no way to run any.
package.jsonshippeddev/build/preview/typecheck; CI installed Node and pnpm and then never ran anything with them — not eventypecheck. And its densest logic was module-private, so a runner alone would not have reached it.Four pure modules extracted from DOM-heavy ones, rather than just
exporting in place —attach.tsimports xterm's CSS at module level, which a node test cannot load:tree.tscatalog.tsbuildTree,mergeLinear,compareNodeskeys.tsattach.tstoCtrl,applyModsToChar,cursorSeqpaths.tsdirAutocomplete.tssplitPath,longestCommonPrefixhtml.tsescapeHtml, replacing copies incatalog.ts,spawn.ts,types.tsandattach.ts(esc)CatalogStoregains an injectable socket factory, scheduler and refetch, so the reducer and the backoff schedule are drivable without a server.snapshot()now returns a copy — it returned the live object whileSessionAdded/Removed/Updatedmutated it in place, so listeners could not diff and every single event rebuilt the entire tree.36 tests across 6 files, including the backoff doubling/cap, reset-on-open, Lagged-triggers-refetch, and that
hydratere-issues ids rather than trusting stored ones.New
webCI job — typecheck, test, build. That is the first CI signal the SPA has ever had.Two constraints worth naming:
ws.onclose, nothing reconciles them). It needs a design call, and this PR is already the infrastructure one.Verification
Fifth of nine from the architecture review (#40–#43 merged).
Reviewed
031c80a40b9891b999de00d5405b6dd67865b405. No findings.