The keeper can bring back a seat a browser lost: a new token on the ledger, the old one refused, and a link that carries it in the fragment, minted by <slug>-reseat.sh through a /keeper door Caddy does not proxy; a spectator at a game in play is told both ways back, and the hall asks before forgetting a game in play
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MbtUPJ24P6vc6tE1x6RS3m
This commit is contained in:
1 parent
98e61dc756
commit
ecab471122
13 files changed
+159
-10
No files matched your search
@@ -143,7 +143,14 @@
|
||||
if (c) void goto(`/join/${c}`);
|
||||
}
|
||||
|
||||
/** A game in play asks before it is forgotten: this browser may be the only thing that holds the seat. */
|
||||
let forgetting = $state<string | null>(null);
|
||||
function forget(roomId: string) {
|
||||
if (!games[roomId]?.over && forgetting !== roomId) {
|
||||
forgetting = roomId;
|
||||
return;
|
||||
}
|
||||
forgetting = null;
|
||||
forgetSeat(roomId);
|
||||
seats = allSeats();
|
||||
}
|
||||
@@ -252,7 +259,11 @@
|
||||
<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>
|
||||
{#if forgetting === held.roomId}
|
||||
<span class="forget-ask">This browser may be all that holds your seat. <button type="button" class="quiet" onclick={() => forget(held.roomId)}>Forget it</button> <button type="button" class="quiet" onclick={() => (forgetting = null)}>Keep it</button></span>
|
||||
{:else}
|
||||
<button type="button" class="dismiss" title="forget this game" onclick={() => forget(held.roomId)}>×</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
@@ -520,6 +531,11 @@
|
||||
background: rgba(240, 165, 58, 0.08);
|
||||
}
|
||||
|
||||
.forget-ask {
|
||||
font-size: 0.85em;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.ledger-row.your-move .ledger-info {
|
||||
color: var(--ember);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ function heldSeats(): Record<string, HeldSeat> {
|
||||
}
|
||||
}
|
||||
|
||||
/** A seat the keeper sent in a link, `#seat=<seat>.<token>`: a fragment stays in the browser, out of every log. */
|
||||
export function seatInLink(hash: string): HeldSeat | null {
|
||||
const m = /^#seat=([^.]+)\.([A-Za-z0-9_-]+)$/.exec(hash);
|
||||
return m ? { seat: m[1], token: m[2] } : null;
|
||||
}
|
||||
|
||||
export function seatFor(roomId: string): HeldSeat | null {
|
||||
return heldSeats()[roomId] ?? null;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { goto, replaceState } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import Board from '$lib/components/Board.svelte';
|
||||
import Lobby from '$lib/components/Lobby.svelte';
|
||||
import ReportSlip from '$lib/components/ReportSlip.svelte';
|
||||
import Preferences from '$lib/components/Preferences.svelte';
|
||||
import { seatFor } from '$lib/net/client';
|
||||
import { rememberSeat, seatFor, seatInLink, trustSeat } from '$lib/net/client';
|
||||
import { NAME_MAX, playerName, rememberName, Room } from '$lib/net/room.svelte';
|
||||
|
||||
const roomId = $derived((page.params.code ?? '').toUpperCase());
|
||||
@@ -30,6 +30,13 @@
|
||||
const id = roomId;
|
||||
room = null;
|
||||
error = '';
|
||||
// A link from the keeper carries a seat; it is kept, and the token taken out of the address bar.
|
||||
const sent = seatInLink(location.hash);
|
||||
if (sent) {
|
||||
rememberSeat(id, sent);
|
||||
trustSeat(id);
|
||||
replaceState(location.pathname + location.search, {});
|
||||
}
|
||||
const held = seatFor(id);
|
||||
const arrive = held ? Room.resume(id, held) : Room.watch(id);
|
||||
joining = !held;
|
||||
@@ -171,6 +178,10 @@
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if watching && room?.started && !room.view.over}
|
||||
<p class="notice muted">Had a seat at this table on another device? Transfer it from there and claim the phrase in the hall, or <a href="mailto:eric@ericwagoner.com?subject={encodeURIComponent(`My seat in room ${roomId}`)}">write to the keeper</a> for a link that brings it back.</p>
|
||||
{/if}
|
||||
|
||||
{#if room && !room.started}
|
||||
<Lobby {room} {link} {copied} {copyLink} />
|
||||
{:else if room}
|
||||
|
||||
Reference in new issue
Block a user