statusBadgeText(email) after deactivating a user hung for the full test
timeout: ManageUsersContent.handleEditUser() refetches the list with the
current statusFilter ('active' by default) after every save, so the
just-deactivated row silently drops out of view — the locator then waits
forever for a row that filter will never show again (no actionTimeout is
configured, so only the outer test timeout eventually kills it).
Add ManageUsersPanel.showAllUsers() and switch to it before reading the
badge. Also gave the test more headroom (60s -> 120s) since it drives two
full UI logins plus create/edit/verify against a shared-CI DEV LXC.
Verified: full local suite green (37 passed, 6 skipped) against DEV.
CI's first run showed the Edit button's aria-label exact-match locator
timing out against the live table even though the same row resolved
fine for badge-text reads moments earlier — switch to positional
lookup (Edit is always the first action button, Deactivate/Delete the
last) instead of relying on exact accessible-name matching.
admin.manage-users.spec.ts only opened/closed the panel; the actual
create/edit/delete flow through the UI was untested. Adds a page object
(ManageUsersPanel) and two specs: full CRUD through the panel, and a
causal cross-session check that deactivating a user via the admin UI
immediately revokes that user's already-open session (NextAuth jwt
callback re-checks isActive on every request, per issue #57's fix).
Every run creates and tears down its own disposable e2e-manage-test-*
account since punimtagdev shares its Postgres DB with MirrorMatch.
- 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).
Bump @levkin/playkit to v0.3.1 (network interception helpers) and land the
work it unblocked: startNetworkErrorMonitor on gallery/search/upload/manage-
users, Favorites + People filter UI e2e (storageState), and a fix for the
lingering `res.data: unknown` / Mailpit-vs-Mailtrap type errors in the mail
and catalog specs. Also adds Vitest coverage + a CI job for admin-frontend,
which had no test infra before this.
- Wire E2E_API_USERNAME/PASSWORD (admin) + E2E_API_VIEWER_USERNAME/PASSWORD
(viewer) into the Gitea Actions e2e job so FastAPI-authed specs run in CI.
- Add api.role-permissions.spec.ts: viewer 403 vs admin 200/200/200 on
/api/v1/users, /api/v1/role-permissions, and /api/v1/photos/bulk-delete
(safe no-op via a non-existent photo id — proves the gate, not deletion).
- Add gallery.search-filters.spec.ts: tag_id and person_id filters on the
public /api/search route return correct subsets, combined filters narrow
results, and the /search UI tag-filter interaction updates the URL/count.
- Update ROADMAP/README/.env.example for the new env vars and coverage.
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.
Establishes frontend unit testing infra (jsdom env, RTL, jest-dom
matchers, common Radix/jsdom polyfills) and seeds it with tests for
lib/utils.ts, lib/photo-utils.ts, hooks/useFocusTrap.ts, and
ThemeToggle.tsx (46 tests total).
Adds test_api_integration.py (person identification, tag+search,
favorites+search, bulk favorites, and role-permission-grant workflows
chained across multiple endpoints) and test_api_errors.py (404/401/403/422
consistency sweeps, SQL-injection/XSS-style input handling, large-payload
handling) per the two suites the existing tests/API_TEST_PLAN.md already
documented but never implemented.
Along the way, found and documented (via a passing test, not a fix) that
GET /api/v1/faces/unidentified has no auth dependency, unlike its sibling
routes.
Adds pytest coverage for metrics, click_log, role_permissions, videos,
pending-identifications, pending-linkages, and reported-photos — the
last three required extending conftest with a second (auth) test
database + DDL mirroring the Prisma schema-auth.prisma tables, since
those endpoints read/write DATABASE_URL_AUTH via raw SQL.
pending-photos.py is intentionally left uncovered for now (heavy
filesystem/hash-based duplicate-detection logic needs its own mocking
strategy) and tracked as a follow-up.
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).
Add next-themes light/dark/system theming with an accessible toggle
in every header, and close out an a11y pass across the viewer app:
skip-to-content link, main landmarks and heading hierarchy on every
route, focus-trapped dialog semantics for the photo viewer modals,
aria-label/live-region coverage for dynamic messages and icon
buttons, and dark: variants for previously light-only colors.
- NextAuth session/jwt callbacks no longer return null (the client
useSession() hook chokes on a null session, crashing the app on
sign-out for deactivated/refreshed sessions) — fixes#57
- UserMenu/Header sign-out handlers close the Manage Users overlay and
popover before calling signOut(), and no longer let a rejected
signOut() promise go unhandled
- Added an app-level error boundary as a safety net for any remaining
client render errors
- Registration and forgot-password flows now tell the user honestly
when the confirmation/reset email failed to send instead of always
claiming success — related to #56
- lib/email.ts throws a clear, actionable error when neither
SMTP nor Resend is configured, instead of a cryptic SDK error
- Documented required SMTP/Resend env vars in DEPLOYMENT_CHECKLIST.md
(were missing entirely, which is why no confirmation/reset emails
were ever sent in deployed environments) — fixes#56
- #45: re-read local folder when recursive checkbox toggles
- #47: clear Identify crop spinner when image is already cached
- #46: search selected people by person_ids; AND for "First Last"
- #21: enqueue network import without blocking API walk
- #26: serve resized grid thumbnails for photos
- #30/#31/#33: re-enable small-face filters in auto-match