Commit Graph

10 Commits

Author SHA1 Message Date
NilsBriggen 99c7657f96 UI overhaul "Living Codex" E: per-screen layouts
Ported all 9 screens to the prototype layout (visual only; all data wiring, text,
aria-labels, and testids preserved) via parallel agents + central verification:
- Dashboard: editorial hero, party roster w/ HP meters + avatars, quests, threats, rolls.
- Campaigns: cover-art deck cards + gilt rule + footer.
- Character sheet + roster: hero header, StatCoin abilities, prof rows; grimoire cards.
- Combat: glowing initiative rows (PC gilt / foe ember), condition badges, log.
- Dice: die-pool glyphs, adv/dis segmented toggle, crit/fumble stage, history.
- Compendium: Spectral stat blocks (ember headers, ability row).
- Settings: grouped icon-tile cards. Live Session: room-code card, seat grid, player board.
- Battle Map: carded list + tooled editor chrome (canvas untouched).

Regression fixes after the ports: dice die-buttons aria-label `d4` not `Roll 1d4`
(was colliding with the primary Roll button); map "Open player view" link uses an
ExternalLink icon (test updated, ↗ glyph removed); seat-claim card gets a stable
data-testid="seat-option" (redesign moved rounded-lg→rounded-xl).

223 unit + 34 e2e + 2 realtime green; tsc + eslint + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:37:00 +02:00
NilsBriggen 9b5c63f23c P15a: hosting a shared session requires a signed-in GM
- The Host control now checks cloudUsername(): when the GM isn't signed in it shows
  "🔒 Sign in to host" (→ Settings) instead of "📡 Host". Players still join as guests.
- Realtime e2e: the GM marks itself signed in before hosting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:35:54 +02:00
NilsBriggen d6eda054bf P14d: guest display names for players
- Players set a display name in the session panel ("Your name"); it persists
  (sessionStore.guestName) and is sent via a new `setName` message on join + edit.
- Server stores the name per connection and uses it in the roster + chat; the roster
  entry now also carries the player's character, so the GM sees "Alice · Lia".
- Realtime e2e: a player names themselves and the GM's roster/share update to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:28:32 +02:00
NilsBriggen 4e13a5bdf4 P14c: targeted private share (handout/note/image) from the roster
- GM clicks a player's 📨 in the session roster → composer (title/body/image via
  resizeToMax) → sendPrivateHandout to that player only (server-routed; non-
  recipients never receive it). Covers "share a secret map/note to the scout".
- The recipient sees a prominent "📨 Just for you" card on their player view
  (PlayerBoards renders playerSessionStore.privateHandout, image inline).
- Realtime e2e: GM shares a private note; only the targeted player sees it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:19:36 +02:00
NilsBriggen e562a270d1 P14b: session chat (table + whispers) + saved campaign recap
- Sidebar gains tabs: Chat / Rolls / (GM) Recap. Chat supports table messages and
  private whispers (GM→player via a recipient picker; player→GM via a toggle).
  Server routes table to all and whispers to the target only (+server tests).
- Rolls + chat are persisted to a per-campaign session recap (Dexie v13 sessionLog
  table + sessionLogRepo); the GM's "Recap" tab shows it (survives reloads/sessions).
- wsSync: chat send/receive, optimistic local echo, persistLog for the GM.
- Realtime e2e: GM table message reaches the player; the player's reply reaches the GM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:15:38 +02:00
NilsBriggen b2cf62f642 P14a: global session sidebar + roster + private-channel backend
- Session sidebar (header "☰ Session" toggle, mobile drawer) visible to GM AND
  players: which session you're in, who's here (GM + players), and the live roll
  feed. The GM's own public rolls now mirror into the local feed too.
- Server roster broadcast to everyone (sendRoster on join/seatGrant/disconnect/
  host-resume); includes the GM. Players now see the roster, not just the GM.
- Targeted private channel (backend, wired to UI next): privateState (GM→server→
  one player only) + privateHandout; image sent INLINE so non-recipients never get
  it. roster/privateHandout schemas + stores + wsSync handlers + senders.
- Adversarial-review fixes: GM included in roster; roster refreshes on GM
  disconnect; privateState returns a forbidden error on auth failure. +3 server tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 17:01:15 +02:00
NilsBriggen d94df41a95 P13: dice — crit/fumble animation + roll visibility
- Crit/fumble emphasis: RollTray (and the Dice page result) highlight a nat-20 /
  critical-success as "✦ Critical ✦" (gold pop + glow) and a nat-1 / critical-
  failure as "✦ Fumble ✦" (red shake). naturalD20() exported from notation.
- DM sees players' rolls: RollFeed now also renders on the Combat page (players'
  rolls already broadcast to the GM).
- Players see the DM's public rolls: new gmRoll message — a hosting GM's rolls
  (Dice page + rollAndShow) broadcast to the table as "GM".
- DM secret-roll toggle (rollStore.secret) on the Dice page suppresses the broadcast.
- Realtime e2e: GM public roll appears in the player's table feed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:35:48 +02:00
NilsBriggen d4ae60f199 P12: persistent live session across navigation
- Player room connection lifted out of the /play route to an app-level hook
  (usePlayerConnection in RootLayout), keyed by a persisted joinIntent — so a
  player stays connected while browsing their sheet, the compendium, anywhere,
  and a reload reconnects automatically.
- Header "● Live: CODE · Leave" badge (PlayerSessionBadge); leaving is explicit
  (leaveRoom clears intent + disconnects). /play renders the table from the store.
- Realtime e2e: player navigates away, badge persists, returns to a synced table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:24:31 +02:00
NilsBriggen 360d9ff842 P21: player seats + two-way live play + offline handoff
Players claim a seat (GM approves) and drive their own character live —
HP/slots/resources/conditions/dice broadcast to the table — via new seat
protocol messages and per-seat ownership enforced in RoomHub. GM persists
player edits (charactersRepo.update → liveQuery → rebroadcast). Offline dev:
"Share with player" encodes a claim link (src/lib/sync/playerLink.ts) → /player
imports the character locally for full-sheet editing, bundled back as an
offlineSnapshot on seat claim for GM review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 12:34:00 +02:00
NilsBriggen a8cb7d65f4 Phase 18: internet realtime collaboration (GM-authoritative) + server
- Shared Zod wire protocol (src/lib/sync/messages.ts): hosted/joined/snapshot/
  mapImage/error; player-safe projections only (enemy HP masked on the GM before
  broadcast via src/lib/combat/playerProjection.ts).
- wsSync adapter (src/lib/sync/wsSync.ts) behind the existing SyncAdapter seam:
  GM hostSession + debounced snapshot broadcast (useSessionBroadcaster), player
  joinSession into an ephemeral playerSessionStore, exponential-backoff reconnect
  with seamless room resume (GM secret). localSync remains the offline default.
- buildSnapshot (src/lib/sync/snapshot.ts) reuses the same projection as the local
  /play view, guaranteeing parity. Player view refactored into shared PlayerBoards;
  /play?room=CODE = networked read-only mode.
- SessionControl in the header: Host (optional password) → shareable room code/link.
- Server (server/): Fastify + @fastify/websocket + @fastify/static, in-memory
  GM-authoritative rooms (crypto room id/secret hashed, optional join password,
  TTL sweep, players strictly read-only), origin allowlist, per-frame size cap +
  rate limit, Zod validation. esbuild bundle (server/build.mjs), tsconfig.server.json.
- Dockerfile (multi-stage) + deploy/ttrpg.compose.yml (own project on external
  'proxy' net, Traefik labels for ttrpg.briggen.dev, non-root, no-new-privileges).
  CSP connect-src now allows same-origin wss:.

Tests: protocol round-trip, room hub (auth/password/resume/TTL/read-only), enemy
masking, Fastify+ws integration (host→join→snapshot, player push rejected), and a
two-context Playwright realtime spec (separate config) — GM hosts, player device
sees live combat with masked enemy HP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:49:31 +02:00