-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprd.json
More file actions
1 lines (1 loc) · 22.8 KB
/
prd.json
File metadata and controls
1 lines (1 loc) · 22.8 KB
1
{"metadata": {"name": "FantasyF1 - Production-Grade Fantasy F1 Application", "version": "1.0", "createdAt": "2026-02-15", "description": "P0 production-grade fantasy Formula 1 app with full features: league management, drafting, team picks, points, admin panel, mobile-first UX"}, "stories": [{"id": "US-001", "title": "Email Validation Registration", "priority": "P1", "description": "As a new user, I want to register with email validation so that legitimate users are verified before accessing the system", "acceptanceCriteria": ["User can register with email, username, password, full name", "Email validation email contains unique validation token", "Token expires after 24 hours", "User cannot log in until email is validated", "Resend validation email option available", "Email service uses Resend or SendGrid", "Validation flow prevents multiple accounts per email", "Frontend shows appropriate states (pending/validated/resend)", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Implement JWT validation token generation", "Use Resend API for email delivery", "Rate limit registration to prevent spam"], "files": ["/app/api/auth/register/route.ts", "/app/auth/validate-email/page.tsx", "/components/auth/RegistrationForm.tsx", "/lib/email/validation.ts"]}, {"id": "US-002", "title": "User Login with JWT Tokens", "priority": "P1", "description": "As a registered user, I want to log in with email and password to receive JWT access and refresh tokens so I can access protected resources", "acceptanceCriteria": ["Login accepts email and password", "Returns JWT access token (expires 15 minutes)", "Returns JWT refresh token (expires 7 days) with secure httpOnly cookie", "Password hashing uses bcrypt with cost factor 12", "Failed login attempts are rate limited (5 attempts per IP per hour)", "Account lockout after 10 failed attempts", "Access token used for API requests (Authorization header)", "Refresh token used to obtain new access tokens", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["JWT signing with HS256 algorithm", "httpOnly cookie for refresh token security", "Redis for rate limiting and lockout tracking"], "files": ["/app/api/auth/login/route.ts", "/app/api/auth/refresh/route.ts", "/lib/auth/jwt.ts", "/lib/auth/password.ts", "/middleware/auth.ts"]}, {"id": "US-003", "title": "Password Reset Flow", "priority": "P2", "description": "As a user, I want to reset a forgotten password via email so I can regain access to my account", "acceptanceCriteria": ["User can request password reset with registered email", "Reset token expires after 1 hour", "Reset email contains secure link with token", "Reset link must be used (cannot be reused)", "Old password is immediately invalidated", "Strong password validation enforced", "Reset confirms success and prompts login", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Single-use reset tokens stored in Redis", "Email transmission via Resend"], "files": ["/app/api/auth/forgot-password/route.ts", "/app/api/auth/reset-password/route.ts", "/app/auth/reset-password/page.tsx", "/lib/email/reset.ts"]}, {"id": "US-004", "title": "User Profile Management", "priority": "P2", "description": "As a registered user, I want to update my profile information, notification preferences, and display settings so my experience is personalized", "acceptanceCriteria": ["User can update full name, username, email", "Email change requires re-validation", "User can set notification preferences (race completed, draft turn, invitations, team updates, points updated)", "User can select theme preference (light, dark, system)", "User can select language preference", "User can set timezone preference", "Profile visibility setting (public/private)", "Show email to league members option", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Profile updates require re-auth for email changes", "Notification preferences stored in user model"], "files": ["/app/api/me/route.ts", "/app/profile/page.tsx", "/components/profile/ProfileForm.tsx", "/lib/notifications/preferences.ts"]}, {"id": "US-005", "title": "Auto-Pick Configuration", "priority": "P3", "description": "As a team owner, I want to configure auto-pick settings for my team so if I miss my draft turn, the system picks based on my strategy", "acceptanceCriteria": ["User can enable/disable auto-pick", "Multiple pick strategies available (highest_ranked, most_points, best_value)", "Auto-pick triggers when timer expires", "Auto-pick respects budget constraints", "Auto-pick respects availability (already picked)", "User can override auto-pick if present", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Auto-pick strategies need to consider both drivers and constructors", "Strategy defaults to best_value if not set"], "files": ["/app/api/me/auto-pick/route.ts", "/components/team/AutoPickSettings.tsx", "/lib/draft/autopick.ts"]}, {"id": "US-006", "title": "Create Fantasy League", "priority": "P1", "description": "As a user, I want to create a fantasy league so I can invite friends and compete in fantasy F1", "acceptanceCriteria": ["Create league with name, description", "Generate unique 6-character league code", "Set league size (2-10 teams)", "Set league privacy (private/public)", "Set scoring rules (customizable JSON config)", "Upload league avatar (optional)", "Choose draft method (manual, snake, random)", "Set draft close condition (FP1, FP2, FP3, qualifying, manual)", "Set scheduled draft date/time", "Set pick timer (default 60 seconds)", "Creator becomes league admin", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["League code must be unique across all leagues", "Scoring rules validated to prevent scores over 100 per race"], "files": ["/app/api/leagues/route.ts", "/app/leagues/create/page.tsx", "/components/leagues/LeagueForm.tsx", "/lib/league/code.ts", "/lib/league/validation.ts"]}, {"id": "US-007", "title": "List and Search Leagues", "priority": "P1", "description": "As a user, I want to browse and search for public leagues so I can find interesting leagues to join", "acceptanceCriteria": ["List all public leagues with pagination (default 20 per page)", "Search leagues by name with fuzzy matching", "Filter by number of teams (shows full/available leagues)", "Show league info (name, member count, creator, privacy)", "Sort by most recent, most members, name", "Private leagues only visible to members", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Use fuzzy matching for search (fuzzywuzzy or similar)", "Paginate database queries for efficiency"], "files": ["/app/api/leagues/route.ts", "/app/leagues/page.tsx", "/components/leagues/LeagueCard.tsx", "/components/leagues/LeagueFilters.tsx", "/lib/leagues/search.ts"]}, {"id": "US-008", "title": "Join League via Code", "priority": "P1", "description": "As a user, I want to join a private league using a 6-character code so I can participate with my friends", "acceptanceCriteria": ["Enter 6-character league code (case-insensitive)", "Verify league exists and is joinable", "Check league hasn't reached max teams (2-10)", "Check user doesn't already have a team in the league", "For private leagues, verify invitation exists or creator allows", "On success, create fantasy team in league", "User becomes league member (default member role)", "Create activity log entry for league", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Case-insensitive code lookup (normalize to lowercase)", "Activity log created via database trigger or transaction"], "files": ["/app/api/leagues/[id]/join/route.ts", "/app/leagues/[id]/join/page.tsx", "/components/leagues/JoinForm.tsx", "/lib/activity/log.ts"]}, {"id": "US-009", "title": "Send League Invitations", "priority": "P1", "description": "As a league admin, I want to invite users via email, username, or user ID so I can grow my league", "acceptanceCriteria": ["Invite via email (send invitation to email address)", "Invite via username (search user by username)", "Invite via user ID (direct invite if known)", "Generate unique invite code for email invitations", "Set invitation expiration (default 7 days)", "Include optional custom message", "Track invitation status (pending, accepted, rejected, expired)", "Limit total invitations per league (prevent spam)", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Email invitations create temporary user record or pending invitation", "Invite code stored for email recipients to register and auto-join"], "files": ["/app/api/leagues/[id]/invite/route.ts", "/components/leagues/InviteForm.tsx", "/lib/email/invitation.ts", "/lib/invitation/validate.ts"]}, {"id": "US-010", "title": "Accept/Reject League Invitations", "priority": "P1", "description": "As a user, I want to accept or reject league invitations so I can control which leagues I join", "acceptanceCriteria": ["View received invitations with league details", "Accept invitation to join league (creates fantasy team)", "Reject invitation to decline join", "View invitation status (pending, accepted, rejected)", "Filter invitations by status", "Expired invitations show as expired", "Activity log tracks accept/reject actions", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Invitation query filters by invited user ID", "Accept creates team and league membership in transaction"], "files": ["/app/api/me/invitations/route.ts", "/app/api/me/invitations/[id]/accept/route.ts", "/app/api/me/invitations/[id]/reject/route.ts", "/components/invitations/InvitationCard.tsx", "/lib/invitation/action.ts"]}, {"id": "US-011", "title": "Manage League Roles", "priority": "P2", "description": "As a league admin, I want to manage member roles (admin, member) so I can enforce access control", "acceptanceCriteria": ["List all league members with roles", "Promote member to admin (requires existing admin permission)", "Demote admin to member (requires existing admin permission)", "League creator always remains admin", "Minimum one admin required in league", "Activity log tracks role changes", "Only admins can modify roles", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Creator admin status immutable", "Role changes require admin role on requesting user"], "files": ["/app/api/leagues/[id]/members/route.ts", "/app/api/leagues/[id]/members/[userId]/role/route.ts", "/components/leagues/MemberList.tsx", "/lib/league/roles.ts"]}, {"id": "US-012", "title": "Create Draft Order", "priority": "P1", "description": "As a league admin, I want to create the draft order for a race so the drafting process is defined", "acceptanceCriteria": ["Set draft method (sequential, snake, random, manual)", "Sequential: fixed order Teams A-B-C-D, repeat", "Snake: A-B-C-D-D-C-B-A pattern", "Random: randomly shuffle team order", "Manual: admin specifies exact order per round", "Draft order stored per race (can vary by race)", "Supports manual override of auto-generated order", "Activity log tracks draft order changes", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Draft order serialized as JSON array of team IDs", "Snake pattern: forward then reverse order each round"], "files": ["/app/api/leagues/[id]/draft/order/route.ts", "/components/leagues/draft/DraftOrderForm.tsx", "/lib/draft/order.ts"]}, {"id": "US-013", "title": "View Live Draft", "priority": "P1", "description": "As a league member, I want to view the live draft so I can see what picks are happening in real-time", "acceptanceCriteria": ["Show current draft order with teams", "Highlight whose turn it is to pick", "Show pick timer countdown (default 60 sec)", "Display already-picked drivers/constructors", "Show draft progress (round X of Y, picks Z of total)", "Real-time updates when picks are made (via Server-Sent Events or polling)", "Draft closes by configured condition (FP1, FP2, FP3, qualifying, manual)", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["SSE for real-time updates (Vercel doesn't support WebSockets)", "Timer countdown uses client-side interval with server timestamp sync"], "files": ["/app/api/leagues/[id]/draft/stream/route.ts", "/app/leagues/[id]/draft/page.tsx", "/components/leagues/draft/DraftBoard.tsx", "/lib/draft/timer.ts", "/lib/sse/emitter.ts"]}, {"id": "US-014", "title": "Make Draft Pick", "priority": "P1", "description": "As a team owner in my turn, I want to pick a driver for my team so I can build my roster", "acceptanceCriteria": ["Only pick when it's my turn (based on draft order)", "View available drivers (not already picked)", "View driver stats (price, team, performance)", "Select driver to pick", "Respect budget constraints (100M total)", "Validate driver not already picked by any team", "Pick recorded with timestamp and pick number", "Auto-pick triggers if timer expires (if enabled)", "Real-time notification of pick to all league members", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Pick validated for turn, budget, availability", "SSE broadcast to all connected clients in league"], "files": ["/app/api/leagues/[id]/draft/pick/route.ts", "/components/leagues/draft/PickDialog.tsx", "/lib/draft/validate.ts", "/lib/draft/transaction.ts"]}, {"id": "US-015", "title": "Auto-Pick on Timer Expiry", "priority": "P1", "description": "As a system, I want to auto-pick for absent team owners so the draft keeps moving", "acceptanceCriteria": ["Timer expiry triggers auto-pick if team owner absent", "Auto-pick uses configured strategy (highest_ranked, most_points, best_value)", "Auto-pick respects budget constraints", "Auto-pick respects driver availability (not already picked)", "Auto-pick triggers whenever timer expires in draft order", "Notification sent to team owner when auto-pick occurs", "Activity log records auto-pick event", "Owner can view their auto-pick history", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Worker function checks for expired timer every second via Vercel Cron", "Strategy applied: highest ranked driver within budget that hasn't been picked"], "files": ["/app/api/leagues/[id]/draft/autopick/route.ts", "/lib/draft/autopick-worker.ts", "/cron/autopick/route.ts", "/components/leagues/draft/AutoPickHistory.tsx"]}, {"id": "US-016", "title": "Create Fantasy Team", "priority": "P1", "description": "As a user joining a league, I want to create my fantasy team so I can participate in drafting", "acceptanceCriteria": ["Team created automatically when accepting invitation or joining via code", "Specify team name (unique within league)", "Budget initialized to 100M", "Owner is set to current user", "League association enforced", "Team starts empty (no picks until draft)", "Activity log tracks team creation", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Auto-created on invitation accept or league join", "Team name validation to prevent duplicates within league"], "files": ["/app/api/leagues/[id]/team/route.ts", "/lib/team/create.ts", "/lib/activity/log.ts"]}, {"id": "US-017", "title": "View My Team Roster", "priority": "P1", "description": "As a team owner, I want to view my drivers so I can track my roster", "acceptanceCriteria": ["Show all drafted drivers for my team", "Display driver details: name, team, number, price, points", "Show points earned per race", "Show total points across all races", "Show budget remaining", "Show team total value (drivers selected)", "Drivers sorted by pick order or points", "Links to driver detail pages", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Team picks query includes driver details and points per race"], "files": ["/app/api/leagues/[id]/team/picks/route.ts", "/app/leagues/[id]/team/page.tsx", "/components/team/RosterCard.tsx", "/components/team/PicksTable.tsx"]}, {"id": "US-018", "title": "View League Leaderboard", "priority": "P1", "description": "As a league member, I want to view the leaderboard so I can track my ranking in the league", "acceptanceCriteria": ["Display all teams ranked by total points", "Show ranking position (1st, 2nd, 3rd, etc.)", "Show team name, owner, total points, budget", "Highlight current user's team", "Show points gap between positions", "Sort by total points (descending)", "Leaderboard updates in real-time as races complete", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Leaderboard query aggregates total points across races for each team"], "files": ["/app/api/leagues/[id]/leaderboard/route.ts", "/components/leagues/Leaderboard.tsx", "/lib/leaderboard/calculate.ts"]}, {"id": "US-019", "title": "Browse Drivers", "priority": "P1", "description": "As a user, I want to browse all F1 drivers with statistics so I can make informed draft decisions", "acceptanceCriteria": ["List all current season drivers (pagination)", "Show driver details: name, number, team, code, country", "Show driver stats: price, total points, wins, podiums, average finishing position", "Show calculated stats: win rate, podium rate, retirement rate", "Filter drivers by team", "Search drivers by name", "Sort by various metrics (points, price, win rate, etc.)", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Driver stats calculated from race results if not stored", "Pagination for performance with large driver lists"], "files": ["/app/api/drivers/route.ts", "/app/drivers/page.tsx", "/components/drivers/DriverCard.tsx", "/components/drivers/DriverFilters.tsx", "/lib/drivers/stats.ts"]}, {"id": "US-020", "title": "Browse Constructors (Teams)", "priority": "P1", "description": "As a user, I want to browse F1 constructors so I can understand the team landscape", "acceptanceCriteria": ["List all constructors (pagination)", "Show constructor details: team name, team code, engine, chassis", "Show stats: world wins, world championships, current points", "Show constructor price for fantasy drafting", "Filter constructors by nationality", "Search constructors by name", "Sort by various metrics", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Constructor stats calculated from driver results"], "files": ["/app/api/constructors/route.ts", "/app/constructors/page.tsx", "/components/constructors/ConstructorCard.tsx", "/lib/constructors/stats.ts"]}, {"id": "US-021", "title": "View Race Schedule", "priority": "P2", "description": "As a user, I want to view the F1 race schedule so I can plan around upcoming races", "acceptanceCriteria": ["List all races in current season (chronological order)", "Show race details: name, circuit name, country, round number, date/time", "Show session times: FP1, FP2, FP3, Qualifying, Sprint, Race", "Display race status (upcoming, in progress, completed)", "Timezone conversion based on user's timezone preference", "Filter by race status", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Timezone conversion uses moment-timezone or date-fns-tz"], "files": ["/app/api/races/route.ts", "/app/races/schedule/page.tsx", "/components/races/RaceCard.tsx", "/lib/races/timezone.ts"]}, {"id": "US-022", "title": "View Race Results", "priority": "P1", "description": "As a league member, I want to view race results so I can see how my drivers performed", "acceptanceCriteria": ["List race finishing order for completed races", "Show driver finishing position, team, points earned", "Show DNF (Did Not Finish) and DNS (Did Not Start) statuses", "Highlight drivers in my fantasy teams", "Calculate fantasy points earned based on scoring rules", "Show fastest lap information", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Race results fetched from Jolpica API or stored locally", "Points calculated based on league scoring rules"], "files": ["/app/api/races/[id]/results/route.ts", "/app/races/[id]/results/page.tsx", "/components/races/ResultsTable.tsx", "/lib/points/calculate.ts"]}, {"id": "US-023", "title": "Calculate Fantasy Points", "priority": "P1", "description": "As a system, I want to automatically calculate fantasy points after each race so leagues can update standings", "acceptanceCriteria": ["Points calculated based on league scoring configuration", "Default scoring includes finishing position and other factors", "Support for custom scoring via JSON configuration", "Points calculated for every fantasy team driver", "Points stored per race per driver per team", "Activity log tracks points calculation", "Notifications sent when points updated", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Default formula from reference app (qualifying - finish + gain) / 10", "Additional point if team member outperforms tied driver by +0.25"], "files": ["/lib/points/formula.ts", "/lib/points/custom.ts", "/cron/points/route.ts", "/lib/activity/log.ts"]}, {"id": "US-024", "title": "Sync F1 Data from Jolpica API", "priority": "P1", "description": "As an admin, I want to sync F1 data from Jolpica API so the system has current season data", "acceptanceCriteria": ["Import drivers for current season", "Import constructors for current season", "Import race schedule for current season", "Import race results after each GP completes", "Calculate driver/constructor statistics after import", "Automated sync via Vercel Cron Jobs (weekly for schedule/standings, post-race for results)", "Manual sync trigger for admins", "Async HTTP client with 30s timeout", "Graceful handling of API errors", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["Jolpica API endpoints: /drivers/{year}, /races/{year}, /races/{id}", "External_id stored for data correlation"], "files": ["/app/api/admin/sync/route.ts", "/lib/jolpica/client.ts", "/lib/jolpica/drivers.ts", "/lib/jolpica/races.ts", "/cron/sync/schedule.ts", "/cron/sync/results.ts"]}, {"id": "US-025", "title": "Manage Notifications", "priority": "P2", "description": "As a user, I want to receive notifications about important events so I stay informed", "acceptanceCriteria": ["Notifications for: draft_turn, race_completed, invitation_received, team_updated, points_updated", "In-app notification bell/badge", "Notification list with pagination (read/unread)", "Mark notifications as read", "Filter notifications by type", "Only for enabled notification preferences", "Notifications cleared when action taken", "Typecheck passes", "Verify in browser using webapp-testing skill"], "technicalNotes": ["SSE for real-time notification delivery", "Notifications stored with foreign key to user and target entity"], "files": ["/app/api/me/notifications/route.ts", "/components/notifications/NotificationBell.tsx", "/components/notifications/NotificationList.tsx", "/lib/notifications/create.ts", "/lib/notifications/stream.ts"]}]}