web/ contains zero test files and its package.json has no test script. Every gate in CI covers the engine; nothing covers the operator-facing app.
Why this is filed now
HT-101 (PR #165) shipped a bug that only manifested in the browser: admin-gating the IMAP endpoints was correct, but web/src/lib/api.ts did not send the acting-Agent header on those two calls, so the connect screen 401'd on every attempt. Engine tests all passed. The service-level smoke test passed. The bug was invisible to both because neither goes through the web layer.
It was caught by a review bot reading the diff, then confirmed by manually driving a browser — not by any gate that will run again tomorrow.
What is currently unprotected
- Server actions in
web/src/lib/*-actions.ts — including which headers they attach, which is exactly what broke
- Session handling and the middleware's auth redirects
- The API client's error mapping (
AppError, status → user-facing copy)
- Every screen's rendering and interaction
Acceptance
- A test runner is configured for the
web workspace and wired into .github/workflows/ci.yml alongside the existing typecheck/build steps.
- Coverage exists for the server-action layer's outgoing request shape — headers included — since that is where the known defect lived.
- At least one test asserts an admin-gated action fails closed without a session, so the HT-101 regression cannot silently return.
- The runner choice matches the engine's (Vitest) unless there is a specific reason not to; a second framework is its own maintenance cost.
Deliberately not in scope
Full component or visual-regression coverage of every screen. The design-system fidelity process (specs/ui/admin-ia.md, the DesignSync byte-comparison) already covers pixels. This issue is about behaviour that no human is checking.
web/contains zero test files and itspackage.jsonhas notestscript. Every gate in CI covers the engine; nothing covers the operator-facing app.Why this is filed now
HT-101 (PR #165) shipped a bug that only manifested in the browser: admin-gating the IMAP endpoints was correct, but
web/src/lib/api.tsdid not send the acting-Agent header on those two calls, so the connect screen 401'd on every attempt. Engine tests all passed. The service-level smoke test passed. The bug was invisible to both because neither goes through the web layer.It was caught by a review bot reading the diff, then confirmed by manually driving a browser — not by any gate that will run again tomorrow.
What is currently unprotected
web/src/lib/*-actions.ts— including which headers they attach, which is exactly what brokeAppError, status → user-facing copy)Acceptance
webworkspace and wired into.github/workflows/ci.ymlalongside the existing typecheck/build steps.Deliberately not in scope
Full component or visual-regression coverage of every screen. The design-system fidelity process (
specs/ui/admin-ia.md, the DesignSync byte-comparison) already covers pixels. This issue is about behaviour that no human is checking.