$duels

Generated 2026-05-30 12:35 GMT+2 ·6 sessions ·147 commits

10h 19min
Active tool time
24,024
Lines of code
147
Commits
8
Features detected
SUMMARYreadme.md

FASTDUELS is a web-based 1v1 mini-game platform where two players race through short head-to-head "Higher or Lower?" matches in about 90 seconds, no install or account required. It runs a SvelteKit PWA frontend against PartyKit Durable Objects on Cloudflare for realtime lobby matchmaking and server-authoritative match rooms, persists users and results in Cloudflare D1 via Drizzle, and signs every WebSocket message with a JWT so reveals stay server-side and cheat-proof. The codebase cleanly splits a reusable platform (auth, lobby, scoring, UI shell in apps/web/ and packages/partykit-server/) from swappable game and content modules (packages/game-engine/, packages/categories/).

Features 8

Sub-3-second guest matches
Drops players from landing into a live 1v1 round with just a nickname in localStorage, so there is nothing to install or sign up for before the first match.
apps/web/src/routes/[[locale=locale]]/play/+page.svelte, packages/partykit-server/src/lobby/lobby-room.ts, packages/partykit-server/src/lobby/queue.ts
Cheat-proof realtime duels
Keeps every answer reveal server-side in the Match Durable Object and signs each WebSocket message with a JWT, so a tampered client cannot peek at the next answer or fake a score.
packages/partykit-server/src/match/match-room.ts, packages/partykit-server/src/match/round-flow.ts, packages/partykit-server/src/auth/jwt.ts
Never wait for an opponent
Falls back to a bot opponent when the matchmaking queue is empty, so a player always gets a full match instead of staring at a lobby spinner.
packages/game-engine/src/bot.ts, packages/game-engine/src/match-runner.ts, packages/partykit-server/src/lobby/queue.ts
Solo streak mode with global leaderboard
Lets players grind an endless single-player run and ranks them on a cross-pool global XP board, so there is a reason to come back even with nobody online.
apps/web/src/routes/[[locale=locale]]/solo/play/+page.svelte, apps/web/src/lib/server/leaderboard.ts, packages/partykit-server/src/solo-leaderboard/solo-leaderboard-room.ts
Accounts that survive a device switch
Bridges a guest UUID into a server-stored profile with Better Auth and passkey sign-in, so match history and ratings carry over instead of vanishing with cleared localStorage.
apps/web/src/lib/server/auth.ts, apps/web/src/lib/server/account-link.ts, infra/d1/schema.sql
Skill-rated match history
Updates a Glicko rating after each match and stores per-quiz skill scores so players can open a history page and watch their rating move over time.
apps/web/src/lib/server/glicko.ts, apps/web/src/lib/server/match-persist.ts, apps/web/src/routes/[[locale=locale]]/history/+page.svelte
Swappable content categories
Ships question packs (Polish YouTubers, top athletes, tech, Drupal) as standalone workspace modules wired into the picker, so adding a new topic is a data drop rather than a code change.
packages/categories/pl-youtubers/src/items.json, packages/categories/tech/src/items.json, packages/game-engine/src/game-module-registry.ts
Bilingual PWA with procedural sound
Serves type-safe Polish and English copy via Paraglide and generates all match SFX in WebAudio, so the app localizes and plays sound without shipping translation strings inline or hosting audio files.
apps/web/messages/pl.json, apps/web/messages/en.json, apps/web/src/lib/game/sound.ts

Stack

svelte framework better-sqlite3 db/orm drizzle-orm db/orm better-auth auth tailwindcss styling vite build/test vitest build/test
Production
@better-auth/passkey ^1.6.9 @opentelemetry/api ^1.9.1 @simplewebauthn/browser ^11.0.0 @simplewebauthn/server ^11.0.0 better-auth ^1.6.9 canvas-confetti ^1.9.3 drizzle-orm ^0.45.2 jose ^6.2.3 openskill ^4.1.1 partysocket ^1.0.2
Dev
@cloudflare/workers-types ^4.20260502.1 @eslint/js ^9.16.0 @inlang/paraglide-js ^1.11.0 @inlang/paraglide-sveltekit ^0.15.0 @playwright/test ^1.49.0 @sveltejs/adapter-cloudflare ^7.2.8 @sveltejs/kit ^2.10.0 @sveltejs/vite-plugin-svelte ^5.0.0 @tailwindcss/vite ^4.0.0 @types/better-sqlite3 ^7.6.13 @types/canvas-confetti ^1.9.0 @types/node ^22.10.0 better-sqlite3 ^12.9.0 drizzle-kit ^0.31.10 eslint ^9.16.0 eslint-plugin-svelte ^2.46.0 globals ^15.13.0 jsdom ^26.0.0 partykit ^0.0.115 partysocket ^1.0.2 prettier ^3.4.0 prettier-plugin-svelte ^3.3.0 prettier-plugin-tailwindcss ^0.6.0 sharp ^0.33.5 start-server-and-test ^2.0.12 svelte ^5.10.0 svelte-check ^4.1.0 svelte-eslint-parser ^0.43.0 tailwindcss ^4.0.0 tsx ^4.19.2 typescript ^5.6.3 typescript-eslint ^8.18.0 vite ^6.0.0 vitest ^2.1.0 wrangler ^4.87.0

Activity timeline

7h 14m 04-26 10m 05-04 28m 05-04 2h 9m 05-05 16m 05-06 9s 05-30

Language breakdown

24,024
lines
TypeScript16,40868%
Svelte7,33731%
CSS1561%
JavaScript970%
HTML260%
$DOCS · 83 files · 35,222 lines

Top files 10

.../play/+page.svelte695 ln
.../match/match-room.ts651 ln
.../leaderboard/+page.svelte575 ln
.../solo-leaderboard/solo-leaderboard-room.ts455 ln
.../server/feedback.ts425 ln
.../lobby/lobby-room.ts402 ln
.../server/feedback.test.ts392 ln
.../account/+page.svelte387 ln
.../play/+page.svelte381 ln
.../match/round-flow.test.ts374 ln

Sessions 6

2026-04-26
7h 14min
3042 tool calls
top: Bash
2026-05-04
10min
251 tool calls
top: Edit
2026-05-04
28min
152 tool calls
top: Bash
2026-05-05
2h 9min
697 tool calls
top: Bash
2026-05-06
16min
269 tool calls
top: Bash
2026-05-30
9s
8 tool calls
top: Bash

Commit log

GIT LOG--oneline
2026-04-26chore: bootstrap monorepo (pnpm workspace, tsconfig base, gitignore)
2026-04-26chore: tighten tsconfig + pin pnpm via packageManager
2026-04-26feat(web): scaffold SvelteKit app with Cloudflare adapter
2026-04-26fix(web): address Task 2 code review follow-ups
2026-04-26feat(web): add Tailwind v4 + theme tokens (dark default)
2026-04-26feat(web): add Paraglide i18n with PL+EN and locale-prefixed routes
2026-04-26feat(partykit): scaffold hello echo room with unit tests
2026-04-26feat(shared-types): add v0 WS protocol types
2026-04-26feat(web): add WS client + connect to hello echo room
2026-04-26feat(infra): add D1 schema, migrations, wrangler config
2026-04-26docs: add Cloudflare deployment instructions
2026-04-26test(web): add Playwright smoke E2E (locale + WS echo)
2026-04-26chore: add ESLint flat config + Prettier
2026-04-26ci: add GitHub Actions workflow (typecheck, lint, unit, e2e)
2026-04-26fix: typecheck across workspace + skip vitest in apps/web until tests exist
2026-04-26feat(web): add nick validation (pure + tested)
2026-04-26feat(web): add guest user store (UUID + nick, localStorage-backed) + vitest config
2026-04-26feat(web): add sound mute preference store
2026-04-26docs: add Phase 2 implementation plan (onboarding + main menu)
2026-04-26feat(web/i18n): add onboarding + menu + how-to-play strings (PL/EN)
2026-04-26feat(web): self-host Geist Sans + Space Grotesk variable fonts
2026-04-26feat(web/ui): add PrimaryButton, BrandLogo, Modal primitives
2026-04-26feat(web/ui): add NavHeader with locale + mute + nick badge
2026-04-26feat(web/ui): add MenuCard primitive (gradient + accent variants)
2026-04-26feat(web): replace WS smoke test with landing + nick entry + menu + how-to-play
2026-04-26test(web): cover onboarding + menu flow with Playwright
2026-04-26fix(web): suppress 3 lint warnings (intentional control-char regex, Svelte 5 $derived, rest props)
2026-04-26docs: add Phase 3 implementation plan (game engine + single-player)
2026-04-26feat(game-engine): bootstrap package + types (GameModule contract)
2026-04-26feat(game-engine): scoring rules (+2 first correct, +1 second, 0 wrong/timeout)
2026-04-26feat(game-engine): seedable PRNG (mulberry32) + pickN helper
2026-04-26feat(game-engine): higher-or-lower module + composeRound + sudden death
2026-04-26feat(game-engine): match runner orchestrator (lifecycle + subscribe)
2026-04-26feat(game-engine): bot opponent (75% accuracy default, 1.5-4s delay)
2026-04-26feat(categories): add pl-youtubers (~40 channels, subscriber counts)
2026-04-26feat(web/i18n): add gameplay + result strings (PL/EN)
2026-04-26feat(web/game): per-nick stats (matches/wins/losses/streak) in localStorage
2026-04-26feat(web/game): match controller + sound/haptic stubs (vs bot mode)
2026-04-26feat(web/ui): add game UI components (Avatar, RoundCard, HigherLowerButtons, ScoreTracker, Countdown, OpponentAnswered, Confetti)
2026-04-26feat(web): add /play (vs bot) + /play/result routes; menu Quick Match wires up
2026-04-26test(web): cover vs-bot match flow + Play again with Playwright
2026-04-26fix(web): result page persistence + valid Geist font
2026-04-26docs(spec): add Phase 7 polish backlog items found during live testing
2026-04-26docs: add Phase 4 implementation plan (real multiplayer via DOs)
2026-04-26feat(game-engine): PublicRoundData + toPublicRound() (anti-cheat helper)
2026-04-26feat(shared-types): WS protocol v1 (lobby + match) + JWT claims
2026-04-26chore(web): remove dead ws-client.ts (replaced by RemoteMatchController in Phase 4)
2026-04-26feat(partykit/auth): JWT sign/verify (HS256 via jose)
2026-04-26feat(partykit/lobby): FIFO queue with category pools (TDD)
2026-04-26feat(partykit/match): server-side round flow (anti-cheat: publicRound, server timing)
2026-04-26feat(partykit/lobby): WS room with FIFO matchmaking + auth gate + /issue-token endpoint
2026-04-26feat(partykit/match): match room (server-auth gameplay, reconnect, rematch, forfeit)
2026-04-26refactor(web/game): extract MatchController interface; rename Bot impl
2026-04-26feat(web/game): RemoteMatchController (WS-backed via PartyKit)
2026-04-26feat(web/i18n): lobby + disconnect + forfeit strings (PL/EN)
2026-04-26feat(web): lobby UI + /play branching (online vs bot mode)
2026-04-26test(web): two-tab Playwright E2E for online match + cancel
2026-04-26fix(web/game): drop unused Answer import in bot-controller
2026-04-26fix(web): properly unsubscribe controller state on cleanup
2026-04-26docs(spec): document Phase 4.1 hardening items (prod multiplayer verification, match-token, DO storage)
2026-04-26docs: add Phase 5 plan (friend invite + rematch)
2026-04-26feat(web/game): rematch support — extend controller interface, wire up remote rematch flow
2026-04-26feat(web/i18n): friend invite + rematch strings (PL/EN)
2026-04-26feat(web/i18n): friend invite + rematch strings (PL — second pass)
2026-04-26feat(web/ui): FriendInviteModal + ResultOverlay components
2026-04-26feat(web): friend invite flow + inline result overlay + rematch wired up
2026-04-26test(web): Playwright E2E for friend invite flow
2026-04-26fix(web/menu): wire 'Play with friend' to /play?mode=friend (Task 7 step missed in Phase 5 commit)
2026-04-26docs: add Phase 6 plan (categories #2 + #3 + picker)
2026-04-26feat(categories): add top-athletes (~76 athletes, IG followers)
2026-04-26feat(categories): add tech (~57 items: LLMs, dev tools, frameworks, multi-attr)
2026-04-26feat(server+web): wire 3 categories to match DO via WS query + lobby pass categoryId in match_found
2026-04-26feat(web): category picker + /play passes selected category to controllers/lobby
2026-04-26docs: add Phase 7 plan (polish + soft launch prep)
2026-04-26feat(web): Phase 7 polish — round timer, sudden death badge, loss animation, WebAudio SFX (8 sounds)
2026-04-26docs: README v1.0 deployment runbook + soft launch checklist
2026-04-26security: HIGH+MEDIUM hardening (auth-001, config-001, config-002, exposure-001, rate-limit-001, business-logic-001/002)
2026-04-26feat(web): UX overhaul — explicit round question, KNOWN/GUESS cards, contextual buttons, result feedback, stats, tutorial
2026-04-26feat(web): Stadium System redesign — sports-broadcast HUD, safety orange, industrial mono
2026-04-26ux(web): readability pass — bigger type, isolated question block, real 4 s countdown
2026-04-26ux(web): bigger landing stats, live wall clock, full-width KNOWN/GUESS header, redesigned how-to-play
2026-04-26ux(web): epic /play/result — hero plate + headline stats + mini receipt + locale-aware sizing
2026-04-26feat(web): ShareSheet, privacy page, bug-report header link — pre-launch polish
2026-04-26docs(scheduler): seed handoff + per-run prompt for autonomous Phase 7.1 + post-MVP work
2026-04-26auto(7.1.A): add MatchSnapshot serialize/fromSnapshot on ServerMatch
2026-04-26auto(7.1.A): wire DO storage rehydration for MatchRoom
2026-04-26auto(7.1.A+7.1.B): close 7.1.A docs flip, scaffold validated /og endpoint
2026-04-27auto(7.1.B): render real Stadium-styled OG SVG body
2026-04-27auto(7.1.B): wire live /og share URL into share text on last line
2026-04-27auto(7.1.B): add OG / Twitter meta tags to app.html
2026-04-27auto(7.1.C): add test:e2e:preview infra for production-bundle Playwright
2026-04-27auto(7.1.C): close 7.1.C with preview-build spec + production-verification doc
2026-04-27auto(PMVP.A): add youtubeChannelId field to pl-youtubers items
2026-04-27auto(PMVP.A): add fetch-yt-avatars script + operator README
2026-04-27auto(PMVP.B): add openskill rating helpers + tests
2026-04-27auto(PMVP.B): add rating field + lazy migration to guest-user store
2026-04-27auto(PMVP.B): wire applyMatch + setRating into online/friend match end
2026-04-27auto(PMVP.B): render rating chip in NavHeader + delta on result hero
2026-04-27auto(follow-on): fix app-html strict-null + add rating-chip preview e2e
2026-04-27chore: ignore .claude/ local config dir
2026-05-01feat(solo): add singleplayer streak mode + global leaderboard (#1)
2026-05-01fix(solo): rename party `solo-leaderboard` → `solo` (PartyKit bundler can't import a hyphenated identifier)
2026-05-02feat(account): phase 1 — D1 + Better Auth scaffold (#2)
2026-05-02ci: drop hardcoded pnpm version
2026-05-02fix(security): pre-launch hardening + unblock prod build (#3)
2026-05-03build: migrate to Workers Builds format (wrangler deploy + assets binding) (#4)
2026-05-03fix(auth): unblock Better Auth on Cloudflare Workers (#5)
2026-05-03fix(web): security headers via SvelteKit hook (Workers Builds compat) (#6)
2026-05-03fix(web): absolute asset paths + allow Google Fonts in CSP (#7)
2026-05-03fix(csp): use SvelteKit kit.csp so inline hydration script is allowed (#8)
2026-05-03docs(devops): add Cloudflare deploy & DevOps guide (#9)
2026-05-04fix(ux): bug-report link, /account explainer, honest landing stats (#10)
2026-05-04fix: prod default for PUBLIC_PARTYKIT_HOST + drop double locale prefix (#11)
2026-05-04fix(menu): Quick Match chip — honest copy, no fake counts (#12)
2026-05-04fix: roadmap snapshot + bug batch (B1, B5, B6, B7) (#13)
2026-05-04fix(nav): nick chip → /account; nick page pre-fills current nick (#14)
2026-05-04feat(account): phase 2 — server-stored profile + JWT bridge (#15)
2026-05-04feat(account): phase 3 — match + solo persistence in D1 (#16)
2026-05-04feat(mvp): cleanup + Glicko ELO + /history page + passkey support (#17)
2026-05-04fix(partykit): friend-invite log surface jose error message (#18)
2026-05-04fix(invite): preserve deep-link query params across nick redirect (#19)
2026-05-04docs: spec for multi-choice quiz game mode + roadmap entry (#20)
2026-05-04feat: multi-choice game mode (Drupal/PHP quiz) (#21)
2026-05-04fix(partykit): add drupal-php and drupal to KNOWN_CATEGORY_IDS (#22)
2026-05-04feat: in-app feedback forms (general + per-question) with Resend (#23)
2026-05-04fix(solo): use SoloRoundLog.category.id instead of fragile qid prefix derive (#24)
2026-05-04feat: user nationality + per-quiz skill scores (#25)
2026-05-04debug(solo): instrument skill upsert with stage tracking + try/catch (#26)
2026-05-04fix(solo): accept multi-choice numeric answers in submit-run validation (#27)
2026-05-04fix(csp): add worker-src 'self' blob: to allow canvas-confetti web worker (#28)
2026-05-04fix(quickmatch): filter CategoryPicker to HL-only categories (#29)
2026-05-04feat(ui): widen desktop layout + responsive fonts + MC question polish (#30)
2026-05-04feat(history): load-more buttons for matches + solo runs (#31)
2026-05-04feat(brand): rebrand DUELS → FASTDUELS + bump BrandLogo sizes (#32)
2026-05-04feat(i18n): migrate hardcoded locale ternaries to paraglide keys (#33)
2026-05-04feat(i18n): EN as default locale (no URL prefix) (#34)
2026-05-04fix(i18n): translate 10 PL keys that still showed English (#35)
2026-05-05docs(devops): CLI runbook for prod ops without CF dashboard (#36)
2026-05-05fix(ux): remove distracting stripes + fastduels CORS + mobile dvh (#37)
2026-05-05feat(multiplayer): enable multi-choice categories (Drupal, Drupal-PHP) (#38)
2026-05-05feat(domain): fastduels.com becomes PRIMARY, duelack.com → 301 (#39)
2026-05-05fix(mc): align option display-order with correctAnswer in pickers (#40)
2026-05-05ci: fix sqlite tests + auto-deploy web/partykit on push to main (#41)
2026-05-06feat(ux): sticky Play CTA on category picker + side-by-side red/green HL buttons (#43)
2026-05-06feat(ux): sticky game buttons during play + 2-row mobile header (#44)
2026-05-06feat(leaderboard): cross-pool global XP ranking + empty-state CTAs (#45)
2026-05-06feat(homepage): live stats — dueling now + today/all-time/countries/top category (#46)