The tally: a booklet tab counting the love

A new tab in the help booklet answers "how much is this getting
played?" from the ledgers themselves: games chronicled (begun and
fought to a finish), distinct wizards seated, every spell, step, and
punch recorded, time at the table estimated by the clock between
moves (gaps over ten minutes don't count — async games tell the
truth), victories split by treasure-theft versus last standing, the
longest game, the fullest table, and the date the first game was
dealt. Computed server-side over the in-memory rooms with a 60-second
cache, fetched over the socket when the tab opens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 11:31:14 -04:00
co-authored by Claude Fable 5
parent ced4dc5749
commit 85831828d7
5 changed files with 114 additions and 4 deletions
+9
View File
@@ -196,6 +196,7 @@ class Net {
/** Every seat this browser holds, across rooms. */
seats = $state<Seat[]>(loadSeats());
/** Lobby ledger: one summary per live seat. */
stats = $state<Record<string, number | string | null> | null>(null);
games = $state<GameSummary[]>([]);
notificationsEnabled = $state(
typeof Notification !== "undefined" && Notification.permission === "granted",
@@ -297,6 +298,10 @@ class Net {
this.resume({ name: seat.name, roomId: seat.roomId, token: seat.token });
break;
}
case "stats": {
this.stats = msg.stats;
break;
}
case "games": {
this.games = msg.games;
for (const g of msg.games as GameSummary[]) {
@@ -367,6 +372,10 @@ class Net {
}
/** Ask the server how all our games are doing. */
requestStats(): void {
this.send({ type: "stats" });
}
refreshGames(): void {
if (this.seats.length > 0) this.send({ type: "myGames", seats: $state.snapshot(this.seats) });
}