Project Health Scorecard
Product Roadmap
Short Term (Next 2 Sprints)
Migrate hosting to Vercel + GitHub Pages
Moved Next.js app from Render Pro ($49/mo) to Vercel (free). Static marketing site to GitHub Pages. Split auth.config.ts for Edge Runtime.
Hash competition judgePin with bcrypt
Competition judgePin was stored as plaintext. Now bcrypt-hashed with bcrypt.compare for verification. PIN display updated for hashed storage.
TypeScript type augmentation for next-auth
Added module augmentation for next-auth types. Eliminated all unsafe `as unknown as` double-casts across auth files and guards.
Per-user PIN support
Replace shared competition PIN with individual hashed PINs per judge. Currently all judges use the same PIN.
Redis-backed rate limiting
Current in-memory rate limiter resets on deploy. Move to Redis for persistent rate limiting across restarts.
CSP headers
Add Content Security Policy headers. Currently no CSP configured despite OWASP security headers in next.config.
JWT role re-validation
Re-validate JWT role against database on sensitive operations. Currently stale role persists until token expires (24h).
E2E browser tests
Add Playwright tests for critical user flows: login, judge scoring, captain submission, organizer category advancement.
Medium Term (1-2 Months)
Real-time score updates
Replace 15s polling with WebSocket or Server-Sent Events for live score updates on captain and results pages.
Table Organizer role
New TABLE_ORGANIZER role for logistics — receives boxes, distributes to tables per the distribution plan. No judging. Separate from captain.
Competition templates
Save and reuse competition configurations (table count, judge assignments, team slots) as templates for recurring events.
PDF score reports
Generate downloadable PDF reports with standings, per-category breakdowns, and individual judge scorecards.
Offline judge mode
Service worker + IndexedDB for judges to submit scores without internet. Sync when reconnected.
Long Term (3-6 Months)
Multi-competition management
Dashboard for managing multiple concurrent competitions with shared judge pools and cross-event analytics.
KCBS API integration
Direct integration with KCBS systems for judge certification lookup, result submission, and event registration.
Mobile native app
React Native companion app for judges with push notifications, offline scoring, and camera-based box scanning.
Analytics dashboard
Historical scoring trends, judge consistency metrics, team performance across events, and statistical outlier detection.
Session Velocity
Items = features, fixes, and infrastructure changes completed per session.
Next Session Planner
Priority-Ordered Tasks
Security gap — shared PIN means any judge can impersonate another
Stale roles could allow unauthorized access for up to 24h
Low effort, high security value — prevents XSS and data injection
Current in-memory limiter resets on every deploy
Simulation script catches integration bugs but doesn't test real browser flows
Risk Register
Tracked Risks
| Risk | Impact | Likelihood | Status |
|---|---|---|---|
| R-1In-memory rate limiter ineffective on Vercel serverless | Medium | High | Active |
| R-2Shared judge PIN allows impersonation | High | Medium | Monitoring |
| R-3JWT role not re-validated against DB | Medium | Low | Active |
| R-4No CSP headers configured | Medium | Low | Monitoring |
| R-5Prisma v5 pinned — no security patches from v7 | Medium | Low | Monitoring |
| R-6Single Supabase instance — no read replicas | Low | Low | Mitigated |
Audit Recommendations
Prioritized Action Items
Verify every server action rejects unauthenticated and wrong-role requests. Current tests only cover pure utility functions.
Verify all user-facing inputs are sanitized before database writes. Zod validates types but doesn't sanitize HTML/SQL.
Current error.tsx is dashboard-level only. A crash in judging takes down the entire dashboard.
Profile slow queries under load. ScoreCard and Submission tables may need composite indexes for captain dashboard queries.
WCAG pass was done in PR #6 but 20+ components were added since. Focus management and ARIA labels need re-verification.
Run next/bundle-analyzer. Mermaid.js is large — consider lazy loading on /tech and /roadmap pages only.
11 common components + 10 UI primitives with no visual testing. Storybook would catch visual regressions.
Findings History & Patterns
Pattern Analysis
Findings by Category
Key Patterns
- Security was the dominant finding category — auth guards, ownership verification, and input validation were all bolted on after initial build.
- The E2E simulation script discovered correctness bugs that unit tests and code review both missed.
- Two tooling constraints (Prisma v7, shadcn v4) were discovered via build failures rather than documentation — documenting these in CLAUDE.md prevented recurrence.
Timeline (Most Recent First)
Tooling & Workflow
Available Commands
npm run devStart dev server on port 3030npm run buildProduction buildnpm testRun 113 unit tests (Vitest)npm run lintESLint checknpm run db:migrateRun Prisma migrationsnpm run db:seedSeed development datanpm run db:resetReset DB + re-seednpx tsx scripts/simulate-competition.tsE2E simulation (2,000+ assertions)Missing / Planned Tools
Overall Progress
27 total items tracked · 13 completed · 14 remaining
Items by Priority
P1 Items
Per-user PIN support
Replace shared competition PIN with individual hashed PINs per judge. Currently all judges use the same PIN.
JWT role re-validation
Re-validate JWT role against database on sensitive operations. Currently stale role persists until token expires (24h).
8 completed
Auth guards missing from server actions
All 62 server actions had no auth guards — anyone could call any action.
Client-supplied user IDs in actions
Server actions accepted userId as a parameter instead of deriving from session. IDOR vulnerability.
DB writes not wrapped in transactions
Multi-step mutations (distribution approval, category submission) could leave partial state on error.
DQ edge cases in tabulation
E2E simulation found 3 bugs: DQ competitors not handled correctly in tiebreaking, dropped score calculation wrong with DQs.
No rate limiting on login
Brute-force PIN guessing possible. 4-digit shared PIN especially vulnerable.
Captain can't verify table ownership
Captain actions didn't check if the captain actually owned the table they were modifying.
Migrate hosting to Vercel + GitHub Pages
Moved Next.js app from Render Pro ($49/mo) to Vercel (free). Static marketing site to GitHub Pages. Split auth.config.ts for Edge Runtime.
Hash competition judgePin with bcrypt
Competition judgePin was stored as plaintext. Now bcrypt-hashed with bcrypt.compare for verification. PIN display updated for hashed storage.
P2 Items
Redis-backed rate limiting
Current in-memory rate limiter resets on deploy. Move to Redis for persistent rate limiting across restarts.
CSP headers
Add Content Security Policy headers. Currently no CSP configured despite OWASP security headers in next.config.
E2E browser tests
Add Playwright tests for critical user flows: login, judge scoring, captain submission, organizer category advancement.
Real-time score updates
Replace 15s polling with WebSocket or Server-Sent Events for live score updates on captain and results pages.
PDF score reports
Generate downloadable PDF reports with standings, per-category breakdowns, and individual judge scorecards.
5 completed
Missing ARIA labels on interactive elements
Buttons, dropdowns, and modals missing screen reader labels. Keyboard navigation broken on DataTable.
Monolithic action file (1,200+ lines)
competition/actions/index.ts had all 27 actions in one file. Hard to navigate and review.
shadcn v4 generates Tailwind v4 code
Default shadcn CLI generates v4-incompatible code. Must use npx shadcn@1 or manually adjust.
Prisma v7 incompatible with Next.js 14
Prisma v7 uses node: protocol imports which Next.js 14 doesn't support. Must pin to v5.
TypeScript type augmentation for next-auth
Added module augmentation for next-auth types. Eliminated all unsafe `as unknown as` double-casts across auth files and guards.
P3 Items
Table Organizer role
New TABLE_ORGANIZER role for logistics — receives boxes, distributes to tables per the distribution plan. No judging. Separate from captain.
Competition templates
Save and reuse competition configurations (table count, judge assignments, team slots) as templates for recurring events.
Offline judge mode
Service worker + IndexedDB for judges to submit scores without internet. Sync when reconnected.
Multi-competition management
Dashboard for managing multiple concurrent competitions with shared judge pools and cross-event analytics.
KCBS API integration
Direct integration with KCBS systems for judge certification lookup, result submission, and event registration.
Mobile native app
React Native companion app for judges with push notifications, offline scoring, and camera-based box scanning.
Analytics dashboard
Historical scoring trends, judge consistency metrics, team performance across events, and statistical outlier detection.