Usability Hardening Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: executing-plans / TDD. Steps use
- [ ].
Goal: Ship real public accounts, IP/burst abuse protection, and an admin Threads explorer.
Spec: docs/superpowers/specs/2026-07-07-usability-hardening-design.md
Tech: Next.js/TS + vitest; better-sqlite3 + bcryptjs + jose + ioredis.
Global Constraints
public-web⇎internal-webno cross-imports; mirror the D1 pattern with app-local copies.- No raw NIK persisted. Cookie httpOnly + SameSite=Lax, Secure in prod, HS256 via NEXTAUTH_SECRET.
- Abuse checks fail-open on Redis error. Turnstile dormant unless TURNSTILE_SECRET set.
pnpm check+ both web suites green. TDD every route/store.
Feature 1 — Public accounts (public-web)
Task 1.1: deps + auth libs (jwt, session, dukcapil, store)
Files: add bcryptjs,jose to apps/public-web/package.json; create
src/lib/auth/{jwt,session,dukcapil,public-store}.ts; test
tests/auth/public-store.test.ts, tests/auth/jwt.test.ts.
- [x] RED: store tests (create/getByEmail/verifyPassword/unique-email/disabled), jwt sign+verify roundtrip
- [x] GREEN: implement mirroring StaffStore/jwt.ts/session.ts; PublicUserStore has no seed
- [x] Commit
Task 1.2: auth routes
Files: src/app/api/auth/{register,login,me,logout}/route.ts; test tests/auth/routes.test.ts.
- [x] RED: register happy path sets cookie + returns user; duplicate → 409; bad email/short pass/bad nik → 400; login ok/401; me with/without cookie; logout clears
- [x] GREEN: implement
- [x] Commit
Task 1.3: client authStore → cookie/me
Files: src/store/authStore.ts, src/components/chat/doc/UpgradeAccountDialog.tsx, src/hooks/useAuthHydrate.ts (new); test tests/store/authStore.test.ts.
- [x] RED: authStore.login(user) sets state w/o token; hydrate calls /me; logout posts /logout
- [x] GREEN: implement; dialog uses response.user; drop localStorage token
- [x] Commit
Feature 2 — Abuse hardening (public-web)
Task 2.1: IP limit lib
Files: src/lib/anon/ip-limits.ts; test tests/anon/ip-limits.test.ts.
- [x] RED: getClientIp (XFF parse, hops), checkIpLimits burst→day ordering, fail-open, getIpDailyMax override
- [x] GREEN: implement
- [x] Commit
Task 2.2: wire into consume + Turnstile
Files: src/app/api/anon/consume/route.ts, src/lib/anon/turnstile.ts; test tests/anon/consume.test.ts (+ turnstile).
- [x] RED: consume 429 on IP exceed before cookie DECR; dormant Turnstile passthrough; TURNSTILE_SECRET set + bad token → 403
- [x] GREEN: implement
- [x] Commit
Feature 3 — Admin Threads explorer (internal-web)
Task 3.1: page + nav
Files: src/app/(staff)/admin/observe/threads/page.tsx, src/components/admin/observe/ThreadsExplorer.tsx, AdminRail.tsx; test tests/admin/threads-explorer.test.tsx (or route/nav test consistent with repo style).
- [x] RED: explorer renders rows from a mocked traces payload; nav entry present
- [x] GREEN: implement
- [x] Commit
Task 4: env + docs + verify
- [x] Add PUBLIC_DB, ANON_IP_, TURNSTILE_ to
*.env.example(+ commented in live env) - [x]
pnpm check+ both suites green; typecheck/lint clean - [x] ROADMAP.md updated (remaining review items); completion notes; upload md