The credibility pass on Hnefatafl, ported to the template: the ops scripts count traffic through one parser, the reports digest is one program, the plaintext-token fallback and its migration are gone, and the stylesheet holds each shared rule once

From the hnefatafl repo's pass of the same day, everything that touched a
kit-shared file. The visitors digest and the nightly rollup share
deploy/traffic.py, installed to /usr/local/lib/<slug>; the rollup writes
the finished-games count it computed behind "and False". pull-reports.sh
and the reports skill both use deploy/report-digest.ts. The seat line
requires its token hash and the start line its rules revision; the
migration for ledgers written before hashing goes with them. The route
table in server/src/index.ts lists every route; Report, ReportLine and
Tally are declared once in view.ts for both sides; exports nobody
imported are exports no more.

In the client: .small, the × that dismisses, and the frame of the
reading pages are in app.css once; the preferences panel shares the
report slip's modal shape; the room store gains seatEmpty and
seatUnheld, and the lobby and the join page read those instead of
three spellings of their own. The room's moved and awaiting fields
stay: the demo board reads them, and simultaneous rounds are the
contract.

The deploy README no longer describes a browser-only game; the visitors
skill no longer names a /play route; the reports skill's replay call
carries the room's options. The Slack channel id is passed in the
environment rather than filled in as a placeholder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwFKMuQnPAEHJ5yA1q4orh
This commit is contained in:
Eric Wagoner
2026-09-23 20:12:37 -04:00
co-authored by Claude Fable 5.1
parent 6c42e55f59
commit 33ac0ec808
41 changed files with 421 additions and 583 deletions
+6 -8
View File
@@ -22,10 +22,8 @@
let phrase = $state<{ phrase: string; expiresAt: number } | null>(null);
let rematching = $state(false);
const link = $derived(typeof location === 'undefined' ? '' : `${location.origin}/join/${roomId}`);
const link = $derived(`${location.origin}/join/${roomId}`);
const watching = $derived(room?.spectating ?? false);
/** A watcher may still sit while the table is laid and a seat is free. */
const seatFree = $derived(!!room && !room.started && room.view.seats.length < room.view.size);
// Return to a held seat, or take a place in the gallery and offer one.
$effect(() => {
@@ -157,12 +155,12 @@
</p>
{/if}
{#if joining && seatFree}
{#if joining && room?.seatEmpty}
<section class="seat">
<p class="eyebrow">room {roomId}</p>
<h2>Take a seat</h2>
<p>A player has opened this table and is gathering company. Choose a name and sit; the game begins when the host says.</p>
<form class="rename" onsubmit={takeSeat}>
<form class="sit" onsubmit={takeSeat}>
<label class="field">
<span>Your name</span>
<input type="text" bind:value={name} maxlength={NAME_MAX} autocomplete="nickname" required />
@@ -174,7 +172,7 @@
{/if}
{#if room && !room.started}
<Lobby {room} {link} />
<Lobby {room} {link} {copied} {copyLink} />
{:else if room}
<Board {room} />
{:else if !error}
@@ -259,7 +257,7 @@
margin-bottom: 0.4rem;
}
.rename {
.sit {
display: flex;
flex-wrap: wrap;
align-items: center;
@@ -278,7 +276,7 @@
font-style: italic;
}
.rename input {
.sit input {
width: 14em;
}