refactor: self-validating clients, fast validate + separate coverage/smoke (#1504)#1505
Conversation
…vel step (#1504) Previously `validate:launcher` built cli/tui/web and ran the cross-client smokes, so the launcher's "validation" depended on every other client being built — and the launcher had no lint/format/coverage of its own. - clients/launcher: add a web-style `validate` (format:check && lint && build && test:coverage) with a Node-only eslint flat config, prettier, and a per-file coverage gate (parse-launcher-argv.ts covered; src/index.ts excluded as binary bootstrap). 100% on the gated file. - Root `validate:launcher` now just delegates: `cd clients/launcher && npm run validate` — no dependency on the other clients. - New top-level `smoke` step (smoke:launcher + smoke:cli + smoke:tui + smoke:web) with NO build commands; `validate` runs it last. Each client's own validation builds its bundle first, so nothing builds twice (cli/tui via test:coverage, launcher via its validate; smoke:web builds dist on demand). - clients/tui test:coverage now builds the TUI bundle (mirroring cli) so the build-free smoke has it available. - CI: replace the standalone cli/tui/launcher build step with a "Validate launcher" step. Docs updated (AGENTS.md, launcher README). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rom validate Extends the launcher self-validate work to the CLI and TUI, and makes the top-level `validate` nothing but the per-client validations — smokes are now a separate top-level target. - clients/cli, clients/tui: add a web-style `validate` with a Node flat eslint config + prettier. CLI lint/format pass as-is. TUI registers eslint-plugin-react-hooks for the classic rules only (rules-of-hooks + exhaustive-deps, which the source already references); the stricter react-hooks@7 rules are not enforced on the interim component surface (#1501). - Avoid double builds: - CLI's e2e spawns the built binary, so its `test:coverage` builds first; `validate` folds that in (`format:check && lint && test:coverage`, no separate `build`). - TUI tests don't spawn a bundle, so `validate` keeps an explicit `build` (for the smoke) and `test:coverage` is pure. - Root: `validate` = validate:web && validate:cli && validate:tui && validate:launcher (each delegates to the client's own validate). `smoke` is no longer part of `validate` — it's a separate top-level target run after a build/validate, with no build commands of its own. - CI: per-client "Validate X" steps + a single "Run cross-client smokes" (npm run smoke). Docs updated (AGENTS.md, cli/tui READMEs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that every client self-validates and the top-level `validate` chains the per-client validations, the root `test` / `test:web` / `test:cli` / `test:tui` / `test:coverage` aggregators are redundant — run `npm run validate` (all) or `cd clients/<name> && npm run validate` (one). `test:storybook` stays (it's the one check not part of validate). CI was already using each client's own scripts, so it's unaffected. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Passing unit runs printed dozens of alarming-but-expected lines that made green runs look broken — all from happy-dom performing real I/O with no server behind it: - `<iframe src="/sandbox.html">` (MCP Apps sandbox) made happy-dom fetch and later unload that URL, emitting ~40 `DOMException [NetworkError/ AbortError]` + `AsyncTaskManager destroyed` lines. Disable child-frame navigation in the unit project (the tests only assert on the iframe element/attributes, not its document), removing the noise with no coverage loss. - App-level renders read `GET /api/config` via useSandboxUrl / useServerListWritable, 404-ing ~76x. Add a benign default `fetch` (empty 200) in the unit setup; tests that exercise fetch still override it. Negative tests that assert on errors already suppress their own console output (e.g. jsonUtils/schemaUtils); CLI, TUI, and the web integration suite were already clean. Unit suite is now noise-free; 2424 web tests pass and the coverage gate is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… own target Each client's `validate` now runs `test` instead of `test:coverage`, so the everyday pre-push check skips v8 instrumentation (and, for web, the slower integration project). The per-file coverage gate moves to a dedicated top-level `coverage` script: coverage = coverage:web && coverage:cli && coverage:tui && coverage:launcher coverage:<name> = cd clients/<name> && npm run test:coverage `validate` still builds each client's bundle (cli via its test `pretest`, the others via an explicit `build`), so `npm run validate && npm run smoke` works unchanged. CI now runs `validate` (fast checks + builds), then `coverage` (the gate), then `smoke` — collapsing the old per-client steps. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI no longer runs `npm run coverage` — the per-file coverage gate is now a local/manual check (`npm run coverage` before pushing). To avoid silently dropping the web integration suite (it only ran via test:coverage, and `validate` runs the unit project only), CI gains a standalone `clients/web` `test:integration` step that exercises the real stdio/HTTP/OAuth/storage paths without coverage instrumentation. CI is now: validate (fast) → web integration → smoke → storybook. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
smoke:launcher was the odd one out — an inline chain of three `node clients/launcher/build/index.js ... --help` commands while smoke:cli / smoke:tui / smoke:web each run a `scripts/smoke-*.mjs`. Extract it to `scripts/smoke-launcher.mjs`, which runs the same `--help` / `--cli --help` / `--tui --help` dispatch checks but now also asserts each prints that mode's usage banner (proving the launcher resolved the right client build) and verifies the required builds exist up front with a clear error. Prints a `smoke:launcher OK — ...` summary in the same style as the rest. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move frequently-run scripts toward the top and rarely-run-by-hand ones (pretest hooks, lint/format internals, etc.) toward the end, consistently across the root and client manifests. Pure reordering — no script names or values changed, and no other manifest content is touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reordering dropped the root `inspector` and `test:storybook` aggregators (intentional — `inspector` is redundant with `web`, and storybook is run from clients/web in CI). Update AGENTS.md so it no longer points at `npm run inspector`, and run `test:storybook` from `clients/web/`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @cliffhall's task in 1m 54s —— View job Code Review
Overall this is a clean, well-motivated tooling refactor with unusually good inline documentation — every non-obvious choice (the coverage-gate split, the happy-dom navigation flag, the launcher Findings1. 2. New 3.
Reasonable tradeoff for quieting ~76 noisy 404s; just flagging that the comment slightly oversells the mechanism. 4. Coverage gate is now CI-invisible (process note, by design) Nits
None of these block merge — they're polish/awareness items. Nice cleanup. |
- TUI format/format:check now also cover the client-root source files (index.ts, tui.tsx, dev.ts) that live outside src/ — `eslint .` already lints them, so they were linted but not format-checked. (cli/launcher have no source outside src/; their root .ts are single-quoted configs intentionally left out of prettier, matching web.) - Reword the setup.ts default-fetch comment: it overstated auto-restore (the unit project sets no restoreMocks/unstubGlobals), and now also flags that the permissive 200 default means a test asserting on a fetch FAILURE must install its own rejecting stub. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review! Addressed the two actionable findings in 5e872b7:
On the by-design / awareness items:
|
|
@claude review |
|
Claude finished @cliffhall's task in 40s —— View job Re-review (post-fixups in
|
|
Thanks — LGTM confirmed, all findings resolved. Ready for merge into |
`install:clients` ran the same `node scripts/install-clients.mjs` as `postinstall`, which fires automatically on `npm install` — so the manual alias was redundant. Drop it and the doc references (AGENTS.md, spec). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This PR made CI run the full `npm run smoke`, which surfaced that smoke:tui fails in headless CI: the Ink TUI throws "Raw mode is not supported" on mount (no TTY) and exits before rendering its first frame. The old CI never ran smoke:tui, so this was latent. smoke-tui.mjs now self-skips when `process.env.CI` is set — the boot/ render check is inherently local-only (a TUI needs a terminal). The TUI is still built and unit-tested in CI; `npm run smoke` stays the single command everywhere (full locally, tui-skipped in CI). Verified `CI=true npm run smoke` is green (launcher/cli OK, tui SKIPPED, web OK). Docs + CI comment updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #1504.
Summary
A build/test tooling refactor that makes every client self-validating, separates the fast
validatecheck from the coverage gate, splits the cross-clientsmokesuite into its own target, and cleans up the noisy web unit-test output. Each section below is independently reviewable.1. Every client self-validates
The launcher, CLI, and TUI now each have their own web-style
validate(the launcher and the cli/tui previously had nolint/format:checkof their own):lint/format/format:check/validate+ a Node-only eslint flat config (web's React/Storybook plugins stripped) and prettier.eslint-plugin-react-hooksfor the classic rules only (rules-of-hooks + exhaustive-deps, which the source already references in disable directives); the stricter react-hooks@7 rules (e.g.set-state-in-effect) are not enforced on the interim component surface tracked in TUI: extend coverage gate to Ink components (lift interim exclusion) #1501.parse-launcher-argv.ts(100%);src/index.tsis excluded as binary bootstrap.validateis just the four per-client validations (validate:web→validate:cli→validate:tui→validate:launcher); each delegates to that client's ownnpm run validate. No cross-client build dependencies.2.
validateis fast;coverageis its own targetvalidaterunstest, nottest:coverage— no v8 instrumentation, and web runs the unit project only.validatestill builds each client bundle (cli via itstestpretest, the others via an explicitbuild).coverage(and per-clientcoverage:web/coverage:cli/coverage:tui/coverage:launcher, each → that client'stest:coverage). Runnpm run coveragewhen you want the gate.e2e.test.tsspawns the built binary, so itstest/test:coveragebuild first and itsvalidatefolds that in (format:check && lint && test, no separatebuild).3.
smokeis a separate top-level targetsmoke(smoke:launcher+ prodsmoke:cli/smoke:tui/smoke:web) is not part ofvalidate— run it after a build/validate (npm run validate && npm run smoke). It has no build commands; the bundles come fromvalidate.smoke:launcheris now a real script (scripts/smoke-launcher.mjs) like the others, asserting--help/--cli --help/--tui --helpeach print the right mode's usage banner (proving the launcher resolved the right client build), not just that they exit 0.4. CI
Collapsed to:
validate(fast: format/lint/build/test + builds all bundles) → webtest:integration(real stdio/HTTP/OAuth/storage paths, no coverage) →smoke→ storybook. The coverage gate is intentionally not run in CI — it's a local/manualnpm run coverage. (Integration is kept in CI as its own step since it otherwise only ran viatest:coverage.)5. Quieter unit-test output
Green web unit runs printed dozens of alarming-but-expected lines from happy-dom doing real I/O with no server:
<iframe src="/sandbox.html">→ ~40DOMException/AsyncTaskManager destroyedlines. Fixed by disabling child-frame navigation in the unit project (tests only assert on the iframe element).GET /api/config→ ~76404s. Fixed with a benign defaultfetch(empty 200) in the unit setup; tests that exercise fetch still override it.Negative tests already suppressed their own console output; CLI/TUI/integration were already clean.
6. Root script cleanup + reordering
Dropped redundant root aggregators (
test,test:web/cli/tui,test:coverage, and the rarely-usedinspector/ roottest:storybook) now that each client self-validates, and reordered the script lists (frequently-run first). Thebuild:*/validate:*/coverage:*/smoke:*families follow one consistent aggregate + per-client pattern.Verification
npm run validate && npm run coverage && npm run smokepasses end-to-end (exit 0): all four client validations, the full coverage gate (incl. web integration, web unit at 2424 tests / gate green), and all four smokes (smoke:launcher/smoke:cli/smoke:tui/smoke:web). Unit output is noise-free.🤖 Generated with Claude Code