think
16px
820px

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-webinternal-web no 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