Files
punimtag/ROADMAP.md
T
ilia b68d7d6507
CI / skip-ci-check (pull_request) Successful in 30s
CI / docker-ci (pull_request) Successful in 31s
CI / python-lint (pull_request) Failing after 32s
CI / secret-scan (pull_request) Successful in 38s
CI / admin-unit (pull_request) Successful in 50s
CI / e2e (pull_request) Successful in 3m11s
CI / viewer-unit (pull_request) Successful in 3m33s
Add confidence fit + quality re-score tooling (Phase 3).
Support distance→confidence knots from identified pairs and match_decisions, keep legacy curve until a knots file is applied, and re-score face quality without re-detecting.
2026-08-05 11:33:42 -04:00

13 KiB

PunimTag roadmap

Living plan for product quality, auth/email reliability, and automation.

Now — shipped / in flight

  • Fix public-host auth redirects (Kolby #57 family) — NEXTAUTH_URL + verify-email
  • DEV mail trap via Mailpit (not Mailtrap SaaS / not Spamhaus-blocked Mailcow for e2e)
  • Playwright e2e suite (e2e/) on @levkin/playkit against punimtagdev.levkin.ca
  • Dedicated e2e user e2e@levkine.ca (Vault / Infisical / Gitea Actions)
  • Vitest unit tests for viewer-frontend in Gitea Actions
  • API smoke + Zod schema checks (health, people, tags, 401 contracts)
  • storageState setup project + real upload smoke
  • FastAPI e2e userse2e (admin, mirrors e2e@levkine.ca) + e2e-viewer (viewer) in FastAPI main DB; E2E_API_USERNAME/PASSWORD + E2E_API_VIEWER_USERNAME/PASSWORD in CI
  • Search / filter e2e — tag + person filters on public gallery (gallery.search-filters.spec.ts)
  • Role permissions e2e — viewer vs admin write gates (api.role-permissions.spec.ts)
  • Ansible FastAPI-e2e work committed — ansible PR #127 merged (provisioning script, SMTP quote fix, vault/docs)
  • Infisical /playkit/punimtag E2E_API_ keys created* — self-hosted build's /api/v3/secrets/raw create route needs client-side E2EE ciphertext (422 on plaintext); fixed by switching to /api/v4/secrets (plaintext) in provision-punimtag-e2e-user.py — see ansible docs/hardening/SECRETS.md
  • Vaultwarden sync for FastAPI e2e loginsPunimTag FastAPI e2e admin/viewer (DEV) entries created
  • Playkit bumped to v0.3.1 — tagged, released (Gitea release job needed a RELEASE_TOKEN secret rename — GITEA_TOKEN is a reserved name Gitea's Actions API rejects), Outline QA & Dev → Playkit page created
  • Network silent-failure monitorstartNetworkErrorMonitor on gallery home, search+filter, upload, Manage Users (network-errors.spec.ts)
  • Favorites + People filter UI e2egallery.filters.authed.spec.ts (uses storageState)
  • admin-frontend Vitest — separate Vite/React app; admin-unit CI job + fastapi-path/media-path lib tests
  • interceptNetworkCall on upload + search filter UI — typed status/JSON spies replace waitForResponse/text scraping (upload.smoke.spec.ts, gallery.search-filters.spec.ts)
  • Widened Zod schemas — FastAPI login TokenResponse + /auth/me UserResponse (api.fastapi-login.spec.ts), gallery SearchResponse (gallery.search-filters.spec.ts)
  • PROD smoke specprod.smoke.spec.ts, opt-in via PROD_BASE_URL; skips (no-op) until the PROD LXC exists — see Ops/docs debt
  • NextAuth (viewer) write gates — third user store (auth DB, hasWriteAccess=false) provisioned via provision-punimtag-e2e-user.py; viewer.write-gates.spec.ts proves 403 (viewer) vs pass-through (admin) on POST /api/faces/{id}/identify
  • Manage Users real CRUD + causal cross-session e2eadmin.manage-users-actions.spec.ts: admin creates/edits/deletes a user through the actual panel (the older spec only opened/closed it), and deactivating a live user immediately revokes their already open session (jwt callback re-checks isActive); disposable e2e-manage-test-* accounts, always cleaned up
  • Timing budgetse2e/timing-budgets.ts expectWithinBudget() gates every timings.measure() call site against a shared BUDGET_MS bucket (api/uiAction/uiLogin/heavyCrud); a step getting order-of-magnitude slower now reds the test instead of only feeding an unwatched sample
  • CI: actions/upload-artifact@v4 pinned to v3 — v4 doesn't work against this Gitea/act runner's artifact backend; report upload on e2e failure was silently broken
  • CI: npm cache corruption retryviewer-unit/admin-unit/e2e all retry npm ci once after npm cache clean --force on first failure (shared act_runner cache has corrupted @next/swc-linux-x64-musl before, redding CI with no product bug)
  • ROADMAP (this file)
  • Person contact fields — optional email/phone on identify (viewer + admin), pending approve UI, Modify person
  • Approve Identified bulk select — Select All Approve/Deny + Approve Next 10
  • Admin pending-queue digest emailADMIN_NOTIFY_EMAIL via noreply@levkine.ca SMTP (throttled)
  • UX Sprint A — match-all filters, slim search payloads, DEV next start — see UX sprint
  • UX Sprint B — mobile gallery UX — see UX sprint
  • UX Sprint C — admin throughput — see UX sprint
  • UX Sprint D — hardening — see UX sprint

Next (near-term)

Ops / docs debt

  • Wire QA/PROD SMTP on live guests when LXCs 9102/9103 exist (make punimtag-sync-smtp ENV=qa|prod) — confirmed via pct list on the Proxmox node: only DEV (9101) exists today, so this (and PROD smoke actually running, and the PROD NEXTAUTH_URL hostname) stays dormant-but-ready until QA/PROD LXCs are provisioned
  • Stop seeding admin@admin.com in docs as the day-to-day login; keep bootstrap scripts but point operators at Vaultwarden PunimTag e2e
  • Set the new E2E_VIEWER_EMAIL/E2E_VIEWER_PASSWORD Gitea secrets from Infisical/Vaultwarden if CI doesn't already have them synced (script sets Gitea directly, so likely already OK — verify on next CI run)
  • pve10 RAM overcommit / CI OOM under bursts — dropped from backlog 2026-07-15 (user); git-ci-01 already at 8 GB (ansible PR #128). Revisit only if OOM storms return.
  • DEV LXC (9101) redeploy — 2026-07-15: e54c60923f0616 (origin/dev, +24 commits incl. c011c80 a11y/aria-labels). Stashed 3 dirty viewer files; npm ci + prisma:generate:all; pm2 restart punimtag-viewer; local + punimtagdev.levkin.ca200. Manage-Users aria-labels present. (Note: punimtag-api still PM2 waiting restart — pre-existing, not introduced by this pull.)

Playkit adoption (pin is v0.3.1 today)

Kit feature In pin? PunimTag use
Zod schema on ApiClient yes health, people/tags catalog, FastAPI login TokenResponse + /auth/me, gallery SearchResponse
saveStorageState / storageStateUse yes setup uses saveStorageState for admin and viewer (hasWriteAccess=false); specs use raw test.use({ storageState }) (storageStateUse optional cosmetic)
playkitFailureArtifacts() yes playwright.config.ts
Mailpit / createMailInbox yes mail specs
interceptNetworkCall / startNetworkErrorMonitor yes both in use — network-errors.spec.ts, upload.smoke.spec.ts, gallery.search-filters.spec.ts
Timing → Pushgateway yes API not wired — needs Pushgateway deployed (ansible deploy/observability/) + CI PLAYKIT_METRICS_* env

Product / coverage gaps

  • PROD smoke actually running — spec exists (prod.smoke.spec.ts) and is verified against a real public host, but stays skipped until PROD_BASE_URL is set (blocked on the LXC existing — see Ops/docs debt)
  • Consider a non-admin manager/editor-role FastAPI + NextAuth user if finer-grained role coverage is ever needed (today's viewer/admin split covers the binary write-access gate)
  • No latency/perf budget assertions anywhere — closed via expectWithinBudget() (see "Now — shipped / in flight"). Still no Core Web Vitals (LCP/CLS/INP) collection — separate gap, not addressed.

Deferred (soak policy)

  • Burn-in / deploy-smoke CLI (playkit ROADMAP v0.4/v0.5+) — playkit's own ROADMAP has an explicit "adoption pause" until the v0.3.1 network helpers + release job soak for a few days in this repo's CI; revisit once that settles rather than adding more moving parts at once.

UX sprint — trust, mobile, admin throughput (2026-08)

Sprint A — quick trust + speed (shipped)

  • Match-all people/tags filters/api/search raw SQL uses GROUP BY + HAVING COUNT(DISTINCT …) when peopleMode=all / tagsMode=all (lib/search-filter-sql.ts + Vitest)
  • Slim gallery face payloads — home SSR + search API return id, photo_id, person_id, location, person names only
  • DEV viewer next startscripts/deploy-viewer.sh; ecosystem.config.js.example uses next start -p 3001
  • Mobile grid actions — download / favorite / report visible on touch (not group-hover only)
  • Mobile filter drawer — slide-over panel on phones; desktop collapsible sidebar unchanged
  • Skeleton grid — pulse placeholders instead of full-page spinner on filter/load
  • Progressive photo viewer — thumbnail first, then full-res swap (local files)

Sprint C — admin throughput (shipped)

  • Identify arrow-key nav — ←/→ prev/next face in queue (skips when focus is in form fields)
  • Toasts + confirm dialogs — Identify, Auto-Match, Approve Identified
  • Auto-Match progress + cancel — indeterminate bar, elapsed timer, abort in-flight request
  • Approve Identified pagination — API page / page_size + batched face→photo lookup; UI 25 rows/page
  • Approve in-app photo preview — lightbox instead of window.open

Sprint D — hardening (shipped)

  • Admin e2e — Identify, Auto-Match, Approve pages + gallery scroll restore (admin.review-pages.spec.ts, viewer.scroll-restore.spec.ts)
  • Core Web Vitals budget — LCP + CLS gates on home gallery (viewer.web-vitals.spec.ts, web-vitals-budgets.ts)
  • Unify / and /search/search redirects to /?… with full query string (peopleMode, tagsMode, etc.)

UX polish (2026-08)

  • Favorite sign-in copy — logged-out heart opens favorites messaging (not report) via SignInRequiredDialog
  • Text contrast — replace misplaced text-secondary (pale wash token) with text-foreground / text-primary on labels, auth dialogs, menus

Face recognition accuracy (2026-08)

  • Phase 1 — quality sharpness — Laplacian variance computed on the face crop (was kernel constant); unit tests in tests/test_face_quality_score.py; error-tag checklist in docs/FACE_MATCH_ERROR_TAGGING.md
  • Phase 2 — trusted refs + stricter Auto-Match + accept/reject log — ref quality floor 0.5; browse/run tolerances 0.5/0.4; auto-accept default 85%; match_decisions table + logging on Save / auto-accept; tests in tests/test_auto_match_phase2.py
  • Phase 3 — recalibrate confidence + re-score quality — fit distance→confidence knots from identified pairs / match_decisions; optional JSON overrides legacy curve; scripts/fit_confidence_calibration.py + scripts/rescore_face_quality.py; docs in docs/FACE_CONFIDENCE_CALIBRATION.md
  • Phase 4 — multi-ref / ensemble embeddings
  • Phase 5 — harder reject of junk detections (tiny/blur/pose)
  • Phase 6 — multi-embedding per person

Later

  • Proper DEV deploy (next start + CI image) instead of long-lived next devscripts/deploy-viewer.sh, ecosystem.config.js.example uses next start -p 3001
  • Ansible/app_setup path aligned with /opt/punimtag/viewer-frontend/.env (not /srv/app)
  • OpenAPI-driven contract suite (playkit / consumer)
  • Multi-browser matrix (firefox/webkit)
  • Hermes/Mattermost report on e2e failure
  • Enable playkit timing metrics in CI once Pushgateway is live

Test map

Layer Where Status
Viewer unit viewer-frontend Vitest CI viewer-unit
Backend unit/integration tests/ pytest existing
E2E browser + mail e2e/tests/* CI e2e
E2E API (unauth + catalog) e2e/tests/api.* CI
E2E FastAPI authed api.fastapi-login.spec.ts CI (secrets set)
E2E role-permission gates api.role-permissions.spec.ts CI (secrets set)
E2E gallery search filters gallery.search-filters.spec.ts CI (public, no login)
E2E favorites + people filter UI gallery.filters.authed.spec.ts CI (storageState)
E2E network silent-failure monitor network-errors.spec.ts CI
E2E NextAuth write gates (viewer vs admin) viewer.write-gates.spec.ts CI (secrets set)
E2E PROD smoke (health + login) prod.smoke.spec.ts dormant — needs PROD_BASE_URL (PROD LXC)
Admin UI unit admin-frontend Vitest CI admin-unit
Timing metrics push gap (ops + CI env)

See also: e2e/README.md, playkit ROADMAP.md / docs/NETWORK.md / docs/IDEAS.md.