The hall puts games in play first for someone coming back, finished ones further down, and reads a game looking up as looking, not unreachable

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:25:07 -04:00
co-authored by Claude Opus 5.5
parent f131f292ae
commit f78ff3ef8c
2 changed files with 48 additions and 18 deletions
+46 -17
View File
@@ -75,12 +75,14 @@
const ready = $derived(name.trim().length > 0);
// Each held seat is looked up once, so the ledger can say whose move it
// is. A seat the server refuses outright is doubted; a second refusal in
// a row forgets it. Silence (a restart, a stale answer) forgets nothing.
// is; until the answer comes, its game is absent and reads "looking". A
// seat the server refuses outright is doubted; a second refusal in a row
// forgets it. Silence (a restart, a stale answer) forgets nothing.
const asked = new Set<string>();
$effect(() => {
for (const held of seats) {
if (held.roomId in games) continue;
games[held.roomId] = null;
if (asked.has(held.roomId)) continue;
asked.add(held.roomId);
api.view(held.roomId, held.token)
.then((v) => {
trustSeat(held.roomId);
@@ -147,6 +149,10 @@
seats = allSeats();
}
/** A game counts as finished once its table says so; until then, even while loading, it is in play. */
const finished = $derived(seats.filter((h) => !!games[h.roomId]?.over));
const active = $derived(seats.filter((h) => !games[h.roomId]?.over));
function status(held: { roomId: string; seat: string }): string {
const g = games[held.roomId];
if (g === undefined) return 'looking';
@@ -188,7 +194,7 @@
}
</script>
<section class="lid">
<section class="lid" class:returning={active.length > 0}>
<div class="lid-head">
<h1>__NAME__</h1>
<p class="pitch">One line that says what the game is.</p>
@@ -241,18 +247,30 @@
<p class="caption">A picture of the game goes here.</p>
</div>
{#if seats.length}
{#snippet gameRow(held: (typeof seats)[number])}
<div class="ledger-row" class:your-move={yourMove(held)}>
<a class="ledger-resume" href={`/join/${held.roomId}`}>
<span class="ledger-code">{held.roomId}</span>
<span class="ledger-info">{status(held)}{#if unread(held) > 0} · <span class="talk-new">{unread(held)} new {unread(held) === 1 ? 'line' : 'lines'} of talk</span>{/if}</span>
</a>
<button type="button" class="dismiss" title="forget this game" onclick={() => forget(held.roomId)}>×</button>
</div>
{/snippet}
<!-- The games still in play, first thing for someone coming back; finished ones wait further down. -->
{#if active.length}
<div class="ledger">
<div class="ledger-head">your games</div>
{#each seats as held (held.roomId)}
<div class="ledger-row" class:your-move={yourMove(held)}>
<a class="ledger-resume" href={`/join/${held.roomId}`}>
<span class="ledger-code">{held.roomId}</span>
<span class="ledger-info">{status(held)}{#if unread(held) > 0} · <span class="talk-new">{unread(held)} new {unread(held) === 1 ? 'line' : 'lines'} of talk</span>{/if}</span>
</a>
<button type="button" class="dismiss" title="forget this game" onclick={() => forget(held.roomId)}>×</button>
</div>
{/each}
{#each active as held (held.roomId)}{@render gameRow(held)}{/each}
</div>
{/if}
{#if finished.length || reports.length}
<div class="ledger past">
{#if finished.length}
<div class="ledger-head">finished games</div>
{#each finished as held (held.roomId)}{@render gameRow(held)}{/each}
{/if}
{#if reports.length}
<div class="ledger-head reports-head">your reports to the keeper</div>
{#each reports as r (r.id)}
@@ -347,14 +365,21 @@
padding: clamp(1.4rem, 3vw, 2.2rem) clamp(1.2rem, 3vw, 2.6rem) 1.8rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-areas: 'head' 'play' 'look' 'ledger';
grid-template-areas: 'head' 'play' 'look' 'ledger' 'past';
gap: 1.2rem 2.2rem;
}
/* Someone coming back sees their games first. */
.lid.returning {
grid-template-areas: 'head' 'ledger' 'play' 'look' 'past';
}
@media (min-width: 901px) {
.lid {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-template-areas: 'head head' 'play look' 'ledger ledger';
grid-template-areas: 'head head' 'play look' 'ledger ledger' 'past past';
}
.lid.returning {
grid-template-areas: 'head head' 'ledger ledger' 'play look' 'past past';
}
}
@@ -474,6 +499,10 @@
padding-top: 0.5rem;
}
.ledger.past {
grid-area: past;
}
.ledger-head {
font-style: italic;
color: var(--bone-dim);