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>
This commit is contained in:
2026-06-08 17:28:32 +02:00
parent 4e13a5bdf4
commit d6eda054bf
8 changed files with 60 additions and 9 deletions
+3 -1
View File
@@ -107,7 +107,7 @@ export const privateHandoutSchema = z.object({
});
export type PrivateHandout = z.infer<typeof privateHandoutSchema>;
export const rosterEntrySchema = z.object({ playerId: z.string(), name: z.string() });
export const rosterEntrySchema = z.object({ playerId: z.string(), name: z.string(), character: z.string().optional() });
export type RosterEntry = z.infer<typeof rosterEntrySchema>;
// ---- messages ----
@@ -129,6 +129,8 @@ export const clientMessageSchema = z.discriminatedUnion('t', [
z.object({ t: z.literal('privateState'), gmSecret: z.string(), targetPlayerId: z.string(), handout: privateHandoutSchema.nullable() }),
// chat: table message (no `to`) or whisper. GM `to` = target playerId; player `to` set = whisper to GM.
z.object({ t: z.literal('chat'), body: z.string().min(1).max(2000), to: z.string().optional() }),
// a player sets their display name (shown in the roster + chat)
z.object({ t: z.literal('setName'), name: z.string().max(40) }),
]);
export type ClientMessage = z.infer<typeof clientMessageSchema>;
+7
View File
@@ -71,6 +71,8 @@ function connect(): void {
for (const [id, dataUrl] of sentImages) send({ t: 'image', gmSecret, id, dataUrl });
} else if (msg.t === 'joined') {
session.set({ role: 'player', status: 'connected', roomId: msg.roomId, error: null });
const guestName = useSessionStore.getState().guestName.trim();
if (guestName) send({ t: 'setName', name: guestName });
} else if (msg.t === 'snapshot') {
usePlayerSessionStore.getState().setSnapshot(msg.snapshot);
// fetch the map image if we don't have it cached
@@ -171,6 +173,11 @@ export function broadcastGmRoll(label: string, expression: string, total: number
}
}
/** Player: set/update the display name shown in the roster + chat. */
export function sendSetName(name: string): void {
if (useSessionStore.getState().role === 'player') send({ t: 'setName', name });
}
/** Send a chat message. `to` set = whisper (GM → that playerId; player → the GM). */
export function sendChat(body: string, to?: string): void {
const text = body.trim();