Closes three items from the outstanding e2e/CI gap list:
- Timing budgets: timings.measure() only ever recorded durations for the
(still-unwired) Pushgateway export — nothing failed CI when a step got
slow. Add e2e/timing-budgets.ts (expectWithinBudget + shared BUDGET_MS
buckets) and wire it into every measure() call site across the suite.
Mail-wait steps are deliberately left unbudgeted (external mail-trap
delivery latency, not a code performance signal).
- actions/upload-artifact@v4 doesn't work against this Gitea/act runner's
artifact backend — pin to v3 for the e2e failure-report upload.
- Shared act_runner npm cache has corrupted platform-native tarballs before
(@next/swc-linux-x64-musl) and reds viewer-unit/admin-unit/e2e with no
product bug involved. All three npm ci steps now retry once after
`npm cache clean --force` on first failure.
Verified: full local suite green against DEV (37 passed, 6 skipped, no
budget assertion failures) before wiring into CI.
Add an e2e/ Playwright harness (via @levkin/playkit) that smoke-tests
the public DEV host: login page loads, sign-out never redirects to a
LAN host, FastAPI /health responds, and forgot-password mail lands in
Mailtrap with a public reset link (Kolby #56/#57 regressions).
Wire it into Gitea Actions as a new `e2e` job (self-hosted runner,
Chromium via Playwright), gated behind the existing skip-ci-check.
Add npm root scripts (install:e2e, test:e2e) and gitignore e2e build
artifacts (node_modules, reports, .env).