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>
This commit is contained in:
2026-06-08 12:34:00 +02:00
parent c28c29a6c6
commit 360d9ff842
21 changed files with 863 additions and 25 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ test('GM hosts a session; a player on another device sees live combat', async ({
const player = await playerCtx.newPage();
await player.goto(`/play?room=${code}`);
await expect(player.getByRole('heading', { name: 'Sample: Lost Mine' })).toBeVisible();
await expect(player.getByText('Lia the Brave')).toBeVisible(); // party synced
await expect(player.getByText('Lia the Brave').first()).toBeVisible(); // party synced (also shown on the seat-claim screen)
// GM starts the seeded encounter → player sees initiative live, enemy HP masked.
await gm.getByLabel('Primary').getByRole('link', { name: 'Combat' }).click();