diff --git a/docs/superpowers/specs/2026-03-12-issue1-fixes-design.md b/docs/superpowers/specs/2026-03-12-issue1-fixes-design.md new file mode 100644 index 0000000..5bd8daa --- /dev/null +++ b/docs/superpowers/specs/2026-03-12-issue1-fixes-design.md @@ -0,0 +1,168 @@ +# Issue #1 Fixes β€” Design Spec + +**Goal:** Address all items in Gitea issue #1 β€” rework predictions to use full ESC entries with tap-to-assign UI, remove Dish of the Nation, rename acts, add player submission indicators, and make lobby code copyable. + +**Source:** https://git.felixfoertsch.de/felixfoertsch/esc/issues/1 + +--- + +## 1. Entry Data Model + +Replace the current country-only lineup with full ESC entries: + +```ts +type Entry = { + country: { code: string; name: string; flag: string } + artist: string + song: string +} + +type Lineup = { + year: number + entries: Entry[] +} +``` + +The data file changes from `esc-2026.json` to `esc-2025.json` using real ESC 2025 entries for testing. Each entry includes the flag emoji in the data file. + +Display format everywhere: `πŸ‡©πŸ‡ͺ Abor β€” SΓΌden` + +## 2. Prediction Model + +Replace the current `{ predictedWinner, top3[], nulPointsPick }` with 4 ordered picks: + +```ts +type Prediction = { + playerId: string + first: string // country code + second: string // country code + third: string // country code + last: string // country code +} +``` + +**Validation:** All 4 picks must be distinct country codes from the lineup. + +**Scoring model** (for future implementation): +- Any of 1st/2nd/3rd picks landing in the actual top 3 β†’ points +- 1st pick matching actual winner β†’ bonus points +- Last pick matching actual last place β†’ bonus points + +## 3. Prediction UI β€” Tap-to-Assign + +**Top section β€” 4 slot cards:** +- "1st Place", "2nd Place", "3rd Place", "Last Place" +- Empty slots show placeholder text +- Filled slots show the entry (flag + artist + song) with a tap-to-remove action + +**Bottom section β€” scrollable entry list:** +- All entries from the lineup +- Already-assigned entries are dimmed/disabled +- Tapping an unassigned entry shows a small picker with only unfilled slots (1st/2nd/3rd/Last) +- After selecting a slot, the entry fills that slot and dims in the list + +**Submit button:** Appears when all 4 slots are filled. After submission or when predictions are locked, the UI becomes read-only showing assigned entries in their slots. + +**Locked state:** When advancing past Pre-Show, predictions lock. The form shows the player's submitted picks (or "Not submitted" if they missed the window). + +## 4. Remove Dish of the Nation + +Strip the entire Dish of the Nation feature: + +**Server:** +- Remove dish WS message handlers from `handler.ts` +- Remove dish methods from `GameManager` +- Remove dish persistence from `GameService` +- Remove dish DB tables (`dishes`, `dish_guesses`) from schema + +**Client:** +- Delete `dish-list.tsx`, `dish-host.tsx`, `dish-results.tsx` +- Remove dish state slices and actions from `room-store.ts` +- Remove dish WS message handlers from `use-websocket.ts` +- Remove dish UI from routes (`play`, `host`, `display`) + +**Shared:** +- Remove dish schemas from `game-types.ts` +- Remove dish WS message types from `ws-messages.ts` + +## 5. Player List β€” Prediction Checkmark + +Add prediction submission status to the player state broadcast: + +- Server: When building room state or game state, include `hasSubmittedPrediction: boolean` per player by checking if a prediction exists in the GameManager +- Client: Player list component renders a checkmark icon (βœ“) next to player names that have submitted predictions +- Visible on all views (play, host, display) + +## 6. Acts Naming + +Rename internal act identifiers and add display names: + +```ts +const ACTS = ["lobby", "pre-show", "live-event", "scoring", "ended"] as const +``` + +| Internal ID | Display Name | Timing Intent | +|----------------|---------------|----------------------------------| +| `lobby` | Lobby | Waiting room, players join | +| `pre-show` | Pre-Show | Before broadcast, predictions | +| `live-event` | Live Event | During broadcast | +| `scoring` | Scoring | After results, leaderboard | +| `ended` | Ended | Party over | + +Host control buttons: "Start Pre-Show", "Start Live Event", "Start Scoring", "End Party". + +Predictions lock when advancing from Pre-Show to Live Event (previously act1 β†’ act2). + +## 7. Lobby Code β€” Copy to Clipboard + +On the display view (and anywhere the room code is shown prominently): + +- Wrap the room code in a tappable/clickable element +- On click: `navigator.clipboard.writeText(roomCode)` +- Show brief "Copied!" feedback (tooltip or temporary text swap) +- Style to indicate interactivity (cursor pointer, subtle hover state) + +## Data Flow Changes + +### Prediction Flow (updated) +1. Client taps entry β†’ selects slot β†’ slot fills in UI +2. Client fills all 4 slots β†’ submits `submit_prediction` with `{ first, second, third, last }` +3. Server validates: all 4 distinct, all valid country codes +4. Server stores in GameManager, persists to DB +5. Server broadcasts updated game state (includes `hasSubmittedPrediction` per player) +6. All clients update player list checkmarks + +### Act Progression (updated names) +``` +lobby β†’ pre-show β†’ live-event β†’ scoring β†’ ended +``` +Predictions lock on `pre-show β†’ live-event` transition. + +## Files Affected + +### Modified +- `packages/shared/src/game-types.ts` β€” entry type, prediction model +- `packages/shared/src/ws-messages.ts` β€” remove dish messages, update prediction message +- `packages/shared/src/constants.ts` β€” act names +- `packages/server/data/` β€” replace `esc-2026.json` with `esc-2025.json` +- `packages/server/src/games/game-manager.ts` β€” remove dish logic, update prediction logic +- `packages/server/src/games/game-service.ts` β€” remove dish persistence, update prediction columns +- `packages/server/src/rooms/room-manager.ts` β€” act name references +- `packages/server/src/ws/handler.ts` β€” remove dish handlers, update prediction handler +- `packages/server/src/db/schema.ts` β€” remove dish tables, update prediction columns +- `packages/server/tests/game-manager.test.ts` β€” rewrite for new model +- `packages/server/tests/ws-handler.test.ts` β€” update for changed messages +- `packages/client/src/stores/room-store.ts` β€” remove dish state, update game state shape +- `packages/client/src/hooks/use-websocket.ts` β€” remove dish handlers +- `packages/client/src/components/predictions-form.tsx` β€” rewrite as tap-to-assign +- `packages/client/src/routes/play.$roomCode.tsx` β€” remove dish UI +- `packages/client/src/routes/host.$roomCode.tsx` β€” remove dish UI +- `packages/client/src/routes/display.$roomCode.tsx` β€” remove dish UI, add copy-to-clipboard + +### Deleted +- `packages/client/src/components/dish-list.tsx` +- `packages/client/src/components/dish-host.tsx` +- `packages/client/src/components/dish-results.tsx` + +### Created +- `packages/server/data/esc-2025.json` β€” full ESC 2025 entry data