Table talk may be private, to the seats named and never the gallery, and a line says the round it was said in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-29 12:16:08 -04:00
co-authored by Claude Opus 5.5
parent 8e35d3a3a6
commit eca8c92964
9 changed files with 71 additions and 18 deletions
+8 -1
View File
@@ -47,7 +47,10 @@
<li class="muted">Nobody has said a word.</li>
{/if}
{#each lines as line, i (line.at + ':' + i)}
<li class:you={!line.name && line.id === room.me}><em>{line.name ? `${line.name} (gallery)` : line.id === room.me ? 'You' : room.nameOf(line.id)}</em> {line.text}</li>
<li class:you={!line.name && line.id === room.me} class:private={!!line.to}>
<em>{line.name ? `${line.name} (gallery)` : line.id === room.me ? 'You' : room.nameOf(line.id)}{#if line.to}<span class="to">, to {line.to.map((id) => (id === room.me ? 'you' : room.nameOf(id))).join(', ')} alone</span>{/if}</em>
{line.text}
</li>
{/each}
</ul>
{#if room.spectating && room.view.galleryTalk}
@@ -110,6 +113,10 @@
color: var(--frost);
}
li.private {
font-style: italic;
}
form {
display: flex;
gap: 0.4rem;
+1 -1
View File
@@ -132,7 +132,7 @@ export function makeApi<State, Input>() {
/** The gallery's view: no token, no seat, nothing any player could not see. */
watch: (roomId: string) => call<View>('GET', `/api/rooms/${roomId}`),
turn: (roomId: string, token: string, input: Input, resolved: number) => call<View>('POST', `/api/rooms/${roomId}/turn`, { token, input, resolved }),
say: (roomId: string, token: string, text: string) => call<View>('POST', `/api/rooms/${roomId}/say`, { token, text }),
say: (roomId: string, token: string, text: string, to?: SeatId[]) => call<View>('POST', `/api/rooms/${roomId}/say`, { token, text, ...(to?.length ? { to } : {}) }),
/** A word from the gallery, signed with a name, when the host allows it. */
sayFromGallery: (roomId: string, name: string, text: string) => call<View>('POST', `/api/rooms/${roomId}/say`, { name, text }),
setGalleryTalk: (roomId: string, token: string, on: boolean) => call<View>('POST', `/api/rooms/${roomId}/gallery`, { token, on }),
+3 -2
View File
@@ -112,9 +112,10 @@ export class Room {
return this.act(() => api.unseat(this.roomId, this.token, seat), 'The bot could not be sent away.');
}
say(text: string): Promise<boolean> {
/** Talk to the whole table, or, naming seats, to them alone. */
say(text: string, to?: SeatId[]): Promise<boolean> {
if (this.spectating) return Promise.resolve(false);
return this.act(() => api.say(this.roomId, this.token, text), 'The table did not hear you.');
return this.act(() => api.say(this.roomId, this.token, text, to), 'The table did not hear you.');
}
/** A word from the gallery, signed; only when the host has let the gallery talk. */
+6
View File
@@ -9,6 +9,12 @@ export interface ChatLine {
text: string;
at: number;
name?: string;
/** A private line: the seats it was said to. Absent, the whole table and the gallery heard it. */
to?: SeatId[];
/** The round the line was said in, counted from one; absent before the game begins. */
turn?: number;
/** A bot's memory of the line (an offer made, an answer heard). Kept in the ledger; never sent. */
note?: unknown;
}
export interface RoomView<State> {