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>
This commit is contained in:
2026-06-08 17:15:38 +02:00
parent 4cb834ad6c
commit e562a270d1
12 changed files with 259 additions and 44 deletions
+8
View File
@@ -58,6 +58,14 @@ test('GM hosts a session; a player on another device sees live combat', async ({
await player.getByRole('button', { name: 'Open session panel' }).click();
await expect(player.getByText(/Who's here \(2\)/)).toBeVisible(); // host + player visible to everyone
// Chat: a GM table message reaches the player; the player's reply reaches the GM.
await gm.getByLabel('Chat message').fill('Welcome adventurers');
await gm.getByRole('button', { name: 'Send' }).click();
await expect(player.getByText('Welcome adventurers')).toBeVisible();
await player.getByLabel('Chat message').fill('Hello GM');
await player.getByRole('button', { name: 'Send' }).click();
await expect(gm.getByText('Hello GM')).toBeVisible();
await gmCtx.close();
await playerCtx.close();
});