add menu rework design spec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
264
docs/superpowers/specs/2026-03-13-menu-rework-design.md
Normal file
264
docs/superpowers/specs/2026-03-13-menu-rework-design.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Menu Rework — App-Style Navigation
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the current ad-hoc tab navigation with a native-app-style bottom navigation bar, driven by TanStack Router nested routes. Clean up the host/player split so the host is simply a player with an extra "Host" tab. Add bingo card completion/redraw flow.
|
||||
|
||||
## Scope
|
||||
|
||||
- In-room views only (`/play/$roomCode`, `/host/$roomCode`)
|
||||
- Display view (`/display/$roomCode`) unchanged — stays full-screen passive
|
||||
- Landing page (`/`) unchanged
|
||||
|
||||
---
|
||||
|
||||
## 1. Route Structure
|
||||
|
||||
### Current
|
||||
|
||||
```
|
||||
/play/$roomCode → flat component, all content inline
|
||||
/host/$roomCode → flat component, all content inline, top tabs (Play | Host)
|
||||
/display/$roomCode → flat component, passive view
|
||||
```
|
||||
|
||||
### New
|
||||
|
||||
```
|
||||
/play/$roomCode/ → layout route: WebSocket, store, header, bottom nav
|
||||
/play/$roomCode/game → game content (predictions, jury, quiz)
|
||||
/play/$roomCode/bingo → bingo card
|
||||
/play/$roomCode/board → leaderboard + player list
|
||||
|
||||
/host/$roomCode/ → layout route: WebSocket, store, header, bottom nav
|
||||
/host/$roomCode/game → same game content as player
|
||||
/host/$roomCode/bingo → same bingo card as player
|
||||
/host/$roomCode/board → same leaderboard as player
|
||||
/host/$roomCode/host → host-only controls
|
||||
|
||||
/display/$roomCode → unchanged
|
||||
```
|
||||
|
||||
- Layout routes handle WebSocket connection, Zustand store hydration, sticky header, and bottom nav rendering.
|
||||
- Child routes consume room/game state from the Zustand store.
|
||||
- Default redirect: `/play/$roomCode` → `/play/$roomCode/game` (same for host).
|
||||
- Game, Bingo, and Leaderboard tab components are shared between player and host — identical behavior. The host is just a player with an extra tab.
|
||||
|
||||
---
|
||||
|
||||
## 2. Sticky Header
|
||||
|
||||
Replaces the current `RoomHeader` component.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ I❤️ESC ABCD 🟢 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Left:** "I❤️ESC" as the app title (text, not emoji — the heart is the Unicode character ❤️)
|
||||
- **Right:** Room code (monospace, bold) + connection status dot (green/yellow/red)
|
||||
- `position: sticky; top: 0; z-index: 50`
|
||||
- `padding-top: env(safe-area-inset-top)` for iPhone notch/Dynamic Island
|
||||
- Act badge removed — the game content makes the current phase obvious
|
||||
|
||||
---
|
||||
|
||||
## 3. Bottom Navigation Bar
|
||||
|
||||
### Layout
|
||||
|
||||
```
|
||||
Player:
|
||||
┌──────────┬──────────┬──────────────┐
|
||||
│ Game │ Bingo │ Leaderboard │
|
||||
└──────────┴──────────┴──────────────┘
|
||||
|
||||
Host:
|
||||
┌────────┬────────┬─────────┬────────┐
|
||||
│ Game │ Bingo │ Board │ Host │
|
||||
└────────┴────────┴─────────┴────────┘
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
- `position: fixed; bottom: 0; left: 0; right: 0; z-index: 50`
|
||||
- `padding-bottom: env(safe-area-inset-bottom)` for iPhone home indicator
|
||||
- Each tab is a TanStack Router `<Link>` to the nested route
|
||||
- Active tab: highlighted with primary color
|
||||
- Inactive tab: muted foreground color
|
||||
- Icons: iOS-style (SF Symbols aesthetic), rendered as SVG. No emoji.
|
||||
- Game → gamepad/play icon
|
||||
- Bingo → grid/squares icon
|
||||
- Leaderboard/Board → trophy icon
|
||||
- Host → wrench/settings icon
|
||||
- Tab label text below each icon
|
||||
- Main content area needs `padding-bottom` matching the nav bar height to avoid content hiding behind it
|
||||
|
||||
### Bingo Tab Availability
|
||||
|
||||
- Tab is always visible and always tappable in all acts
|
||||
- Players can view and familiarize themselves with their bingo card before `live-event`
|
||||
- Tapping squares to mark them is only enabled during `live-event` act
|
||||
- Before `live-event`: card is visible but squares are non-interactive (visual only)
|
||||
|
||||
---
|
||||
|
||||
## 4. Game Tab Content by Act
|
||||
|
||||
Identical for player and host. No nested tabs.
|
||||
|
||||
| Act | Content |
|
||||
|-----|---------|
|
||||
| **Lobby** | Predictions form (editable) |
|
||||
| **Pre-Show** | Predictions form (editable until locked) |
|
||||
| **Live Event** | Jury voting (when round is open) or "Waiting for host to open voting..." |
|
||||
| **Scoring** | Locked predictions (with actual results comparison) + quiz buzzer (when question active) |
|
||||
| **Ended** | Locked predictions + "Thanks for playing!" |
|
||||
|
||||
The nested Jury/Bingo tabs that currently exist inside the Game content during `live-event` are removed — bingo has its own tab, and jury voting is the sole content of the Game tab during live-event.
|
||||
|
||||
---
|
||||
|
||||
## 5. Bingo Tab Content
|
||||
|
||||
### Active Card View
|
||||
|
||||
Shows the player's current bingo card. Same `BingoCard` component as today, with one change:
|
||||
|
||||
- Before `live-event`: squares are rendered but non-interactive (no `onTap` handler)
|
||||
- During `live-event`: squares are interactive (tap to mark)
|
||||
- After `live-event`: card is frozen (same as current behavior)
|
||||
|
||||
### Completion Flow (New)
|
||||
|
||||
When a player completes a bingo line:
|
||||
|
||||
1. **Server detects completion** — the server already detects bingo lines and awards 10 bonus points. Extended to mark the card as `completed`.
|
||||
2. **Card stored** — the completed card moves to a `completedBingoCards` array in game state (per player). Contains: `playerId`, `displayName`, `card` (the full card data), `completedAt` (timestamp).
|
||||
3. **Player sees** — a celebration message ("Bingo!") + "Draw New Card" button.
|
||||
4. **Redraw** — new WS message `request_new_bingo_card`. Server generates a fresh card with different tropes. The old card is already in `completedBingoCards`.
|
||||
5. **New card** — `myBingoCard` in game state updates to the fresh card. Player can continue playing.
|
||||
|
||||
### Data Changes
|
||||
|
||||
Shared types additions:
|
||||
```
|
||||
completedBingoCards: { playerId: string, displayName: string, card: BingoCard, completedAt: string }[]
|
||||
```
|
||||
|
||||
New WS client message:
|
||||
```
|
||||
{ type: "request_new_bingo_card" }
|
||||
```
|
||||
|
||||
Game state changes:
|
||||
- `myBingoCard` continues to represent the current active card
|
||||
- `completedBingoCards` is a new array on `GameState` (visible to all — host needs it for verification)
|
||||
|
||||
---
|
||||
|
||||
## 6. Leaderboard Tab Content
|
||||
|
||||
Replaces both the current `Leaderboard` and `PlayerList` components as separate UI elements.
|
||||
|
||||
- **Lobby** (no scores yet): shows player list with names only — a "who's here" view
|
||||
- **All other acts**: full leaderboard table (rank, name, P/J/B/Q breakdown, total points)
|
||||
- **Scoring explanation**: "How scoring works" box at the bottom (same as current)
|
||||
|
||||
The `PlayerList` component is no longer rendered anywhere else. Every player appears in the leaderboard. The leaderboard tab is the single place to see who's in the room.
|
||||
|
||||
---
|
||||
|
||||
## 7. Host Tab Content
|
||||
|
||||
Vertical stack of cards, only visible to the host. This tab contains all host-exclusive controls.
|
||||
|
||||
### Always Present
|
||||
|
||||
1. **Act Controls** — advance/revert buttons with act-specific labels:
|
||||
- Lobby → "Start Pre-Show"
|
||||
- Pre-Show → "Start Live Event"
|
||||
- Live Event → "Start Scoring"
|
||||
- Scoring → "End Party"
|
||||
- Ended → "Back to Scoring" + re-open option
|
||||
- Revert button available for all acts except lobby
|
||||
|
||||
2. **Display View** — explanation of what the display view is ("Project this on a TV for everyone to see") + the display URL (`/display/$roomCode`) + "Copy Link" button
|
||||
|
||||
### Conditional (by act)
|
||||
|
||||
3. **Jury Host** (live-event) — open/close voting per country (existing `JuryHost` component)
|
||||
4. **Quiz Host** (scoring) — start question, judge answer, skip (existing `QuizHost` component)
|
||||
5. **Actual Results Form** (scoring/ended) — enter final placings (existing `ActualResultsForm` component)
|
||||
6. **Bingo Claims** (new, all acts after live-event starts) — list of completed bingo cards with player name and card preview, for host verification
|
||||
|
||||
### Bottom
|
||||
|
||||
7. **End Party** button — destructive (red), always available except when already ended
|
||||
|
||||
---
|
||||
|
||||
## 8. Display View
|
||||
|
||||
Unchanged. Full-screen passive view, no bottom nav. Continues to show:
|
||||
|
||||
- Lobby: large room code with join instructions
|
||||
- Pre-Show: prediction submission count
|
||||
- Live Event: jury display + bingo announcements + leaderboard
|
||||
- Scoring: quiz display + actual results + leaderboard
|
||||
- Ended: final results + leaderboard
|
||||
|
||||
---
|
||||
|
||||
## 9. Components Affected
|
||||
|
||||
### New Components
|
||||
|
||||
- `BottomNav` — the bottom navigation bar (renders tabs as `<Link>`s, highlights active)
|
||||
- `RoomLayout` — sticky header + bottom nav + content outlet (used by both layout routes)
|
||||
- `BingoClaims` — host-only component showing completed bingo cards for verification
|
||||
|
||||
### Modified Components
|
||||
|
||||
- `RoomHeader` → replaced by new sticky header in `RoomLayout`
|
||||
- `BingoCard` → add read-only mode (disable tapping before live-event)
|
||||
- `Leaderboard` → absorb player list display for lobby state
|
||||
- `PlayerList` → removed from all views (absorbed into leaderboard tab)
|
||||
|
||||
### Shared Between Player and Host
|
||||
|
||||
- Game tab content (predictions, jury voting, quiz buzzer)
|
||||
- Bingo tab content (bingo card)
|
||||
- Leaderboard tab content
|
||||
|
||||
### Host-Only
|
||||
|
||||
- Host tab content (act controls, display link, jury host, quiz host, actual results, bingo claims)
|
||||
|
||||
---
|
||||
|
||||
## 10. Server Changes
|
||||
|
||||
### Bingo Completion
|
||||
|
||||
- `GameManager.tapBingoSquare()` — after marking a square, check for completed lines. If bingo detected:
|
||||
- Move current card to `completedBingoCards` array
|
||||
- Award bonus points (already happens)
|
||||
- Flag card as completed in response
|
||||
|
||||
- New method: `GameManager.requestNewBingoCard(playerId)` — generates fresh card, assigns to player
|
||||
|
||||
- New WS handler: `request_new_bingo_card` message → calls `requestNewBingoCard`, broadcasts updated state
|
||||
|
||||
### Game State Changes
|
||||
|
||||
- Add `completedBingoCards` to game state schema
|
||||
- Include in `getGameStateForPlayer` and `getGameStateForDisplay`
|
||||
|
||||
---
|
||||
|
||||
## 11. Migration / URL Compatibility
|
||||
|
||||
- Old URLs (`/play/ABCD`, `/host/ABCD`) should redirect to `/play/ABCD/game` and `/host/ABCD/game` respectively
|
||||
- TanStack Router can handle this via the layout route's default redirect
|
||||
Reference in New Issue
Block a user