feat: speedrun challenge mode#47
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5b04d722a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| /** Returns the UTC Date at which a speedrun prompt's window closes. */ | ||
| function speedrunDeadline(prompt: { createdAt: Date; deadlineHours: number | null }): Date | null { | ||
| if (!prompt.deadlineHours) return null; |
There was a problem hiding this comment.
Honor zero-hour speedrun deadlines
When deadlineHours is 0 (the docs explicitly suggest this for an immediately expired speedrun), this truthiness check returns null, so the submission route's if (deadline && ...) guard is skipped and the server accepts submissions instead of returning the intended 409. Check for deadlineHours == null rather than falsiness so zero-hour windows close at createdAt.
Useful? React with 👍 / 👎.
|
Review notes before merge:
Keep-it-free goal: ✅ free engagement feature. |
Summary
Adds a speedrun variant to the existing challenge system. A speedrun prompt has a finite submission window (e.g. 24 or 48 hours). After the window closes, submissions are rejected and the UI shows a static "Window closed" badge.
What changed
shared/challenge_schema.tsis_speedrun BOOLEAN NOT NULL DEFAULT FALSEtochallenge_promptsdeadline_hours INTEGER(nullable) tochallenge_promptsinsertChallengePromptSchema(new — was previously missing)server/challenge_routes.tsPOST /api/challenges/submissions— now looks up the prompt and returns409if a speedrun window has expiredPOST /api/challenges/prompts(new) — authenticated route to create prompts including speedrun ones; validated withinsertChallengePromptSchemaGET /api/challenges/prompts/:id/participants(new) — public endpoint returning{ count: number }of distinct submitters for a given prompt; used by the 30-second polling hookclient/src/components/SpeedrunCountdown.tsx(new)HH:MM:SS leftcountdown usingsetInterval; cleans up on unmount; turns amber under 1 hour; shows static red "Window closed" at 0client/src/hooks/useSpeedrunParticipants.ts(new)useQuerywrapper that pollsGET /api/challenges/prompts/:id/participantsevery 30 s; only enabled whenisSpeedrun && window openclient/src/pages/challenge/index.tsxPromptCardcomponent to isolate speedrun logic⚡ Speedrunbadge, liveSpeedrunCountdown, and live participant countwindowClosedmigrations/0010_speedrun_challenges.sql(new)docs/speedrun-challenges.md(new)Local testing guide, seed curl command, scenario table.
Acceptance criteria
⚡ Speedrunbadge + countdown + participant tickerPOST /api/challenges/submissionsreturns409when speedrun window expiredGET /api/challenges/prompts/:id/participantsreturns{ count: number }pnpm check(TypeScript) passes with no new errorsbun testpassesHow to test locally
See docs/speedrun-challenges.md for the full guide.