Skip to content

feat: speedrun challenge mode#47

Open
matthewro7263-hub wants to merge 2 commits into
mainfrom
feat/speedrun-challenges
Open

feat: speedrun challenge mode#47
matthewro7263-hub wants to merge 2 commits into
mainfrom
feat/speedrun-challenges

Conversation

@matthewro7263-hub
Copy link
Copy Markdown
Owner

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.ts

  • Added is_speedrun BOOLEAN NOT NULL DEFAULT FALSE to challenge_prompts
  • Added deadline_hours INTEGER (nullable) to challenge_prompts
  • Exported insertChallengePromptSchema (new — was previously missing)

server/challenge_routes.ts

  • POST /api/challenges/submissions — now looks up the prompt and returns 409 if a speedrun window has expired
  • POST /api/challenges/prompts (new) — authenticated route to create prompts including speedrun ones; validated with insertChallengePromptSchema
  • GET /api/challenges/prompts/:id/participants (new) — public endpoint returning { count: number } of distinct submitters for a given prompt; used by the 30-second polling hook

client/src/components/SpeedrunCountdown.tsx (new)

  • Pure presentational component: shows HH:MM:SS left countdown using setInterval; cleans up on unmount; turns amber under 1 hour; shows static red "Window closed" at 0

client/src/hooks/useSpeedrunParticipants.ts (new)

  • useQuery wrapper that polls GET /api/challenges/prompts/:id/participants every 30 s; only enabled when isSpeedrun && window open

client/src/pages/challenge/index.tsx

  • Extracted PromptCard component to isolate speedrun logic
  • Speedrun prompts show: ⚡ Speedrun badge, live SpeedrunCountdown, and live participant count
  • Submit button is disabled when windowClosed
  • All other behaviour (reactions, feed, SubmitDialog) is unchanged

migrations/0010_speedrun_challenges.sql (new)

ALTER TABLE challenge_prompts ADD COLUMN IF NOT EXISTS is_speedrun BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE challenge_prompts ADD COLUMN IF NOT EXISTS deadline_hours INTEGER;

docs/speedrun-challenges.md (new)

Local testing guide, seed curl command, scenario table.


Acceptance criteria

  • Normal challenges render identically to before (no badge, no timer)
  • Speedrun prompt shows ⚡ Speedrun badge + countdown + participant ticker
  • Countdown turns amber when < 1 hour remains
  • Countdown shows "Window closed" in red when expired
  • Submit button disabled when window is closed
  • POST /api/challenges/submissions returns 409 when speedrun window expired
  • GET /api/challenges/prompts/:id/participants returns { count: number }
  • Participant count updates within 30 s across two browser sessions
  • pnpm check (TypeScript) passes with no new errors
  • bun test passes

How to test locally

See docs/speedrun-challenges.md for the full guide.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@matthewro7263-hub
Copy link
Copy Markdown
Owner Author

Review notes before merge:

  1. Add rate-limiting to the new public GET /api/challenges/prompts/:id/participants route — the 30s polling hook will multiply traffic per viewer; an unauthed public endpoint without limits is abuse-prone. Suggest per-IP token bucket (e.g., 30 req/min) and/or short cache headers (Cache-Control: public, max-age=10).
  2. Confirm CI checks run (currently 0 checks). Block merge until tests for the 409 expired-window path are green.
  3. The new POST /api/challenges/prompts is authed — verify ownership / role check, not just requireAuth, so users can't create prompts under others' challenges.
  4. Index challenge_prompts(is_speedrun, deadline_hours) if the leaderboard cron will scan by these.
  5. UI: ensure the static “Window closed” badge has aria-live so screen readers announce expiry.

Keep-it-free goal: ✅ free engagement feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant