- upload.smoke.spec.ts / gallery.search-filters.spec.ts: replace
waitForResponse/text-scrape with typed interceptNetworkCall spies (status
+ JSON shape) on the real upload POST and the filtered /api/search GET.
- api.fastapi-login.spec.ts: Zod-validate the FastAPI TokenResponse and
/auth/me UserResponse instead of loose casts. gallery.search-filters.spec.ts
gets the same treatment for the Prisma-backed SearchResponse.
- prod.smoke.spec.ts: opt-in (PROD_BASE_URL) health + login-page check on a
real PROD host; skips as a no-op until the PROD LXC exists (none does yet
per `pct list` — see ROADMAP).
- viewer.write-gates.spec.ts: NextAuth (browser-session) hasWriteAccess gate
on POST /api/faces/{id}/identify — viewer 403s, admin passes through.
Provisioned a third, independent auth-DB user (e2e-viewer@levkine.ca,
hasWriteAccess=false) for this via ansible's provision-punimtag-e2e-user.py
(see that repo for the vault/Infisical/Gitea/Vaultwarden side).
Wire viewer-frontend Vitest into Gitea Actions, centralize DEV base URLs
in env-defaults.json, expand playkit API catalog specs, and land auth/mail
flows plus verify-email public-host redirect and idle-logout e2e hooks.
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).