Share a turn with the world: /watch links, cards and chrome included
The share button lands on the instant replay. One click mints a slug — persistent, unguessable, one per (room, turn) — and copies a /watch link anyone can open: the server rebuilds exactly that turn for the nameless SPECTATOR, public knowledge only, none of the rest of the game visible. The page arrives with its OpenGraph card pre-baked into the head (crawlers never run the app): the turn's title and round in the text, and an og.png of the maze itself — cells, walls, doors, warp mouths, homes, standees — painted server-side and PNG-encoded with nothing but node's own zlib. The viewer page wears its chrome: the WIZ-WAR masthead linking home, the turn's title, the reel opening straight into the turn-owner's eyes with a "watch it again" loop, and a footer that says what this table is and deals the visitor in. The share page loads as its own entry so a shared link never drags the full app's socket appetite along; the reel's modal scrim learns to stand in a page instead of over one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
727b9144d0
commit
4838996cf0
@@ -321,6 +321,9 @@ class Net {
|
||||
private turnCounter = -1;
|
||||
/** The turn that already carries an instant-replay eye (one per turn). */
|
||||
private eyeTurn = -1;
|
||||
/** The turn whose moment reel is open (share links point at it). */
|
||||
private momentTurn: number | null = null;
|
||||
private shareResolve: ((url: string) => void) | null = null;
|
||||
private seen: Record<string, number> = loadSeen();
|
||||
/** Room whose live stream this connection has already shown once: states
|
||||
* after the first mark themselves seen while the tab is visible. */
|
||||
@@ -423,6 +426,10 @@ class Net {
|
||||
case "moment":
|
||||
this.moment = msg.steps;
|
||||
break;
|
||||
case "share":
|
||||
this.shareResolve?.(`${location.origin}/watch/${msg.id}`);
|
||||
this.shareResolve = null;
|
||||
break;
|
||||
case "events": {
|
||||
let talk = 0;
|
||||
if (!msg.replayed) this.onFx?.(msg.events as GameEvent[]);
|
||||
@@ -688,9 +695,25 @@ class Net {
|
||||
|
||||
/** Summon one turn's reel by its chronicle turn number. */
|
||||
requestMoment(turn: number): void {
|
||||
this.momentTurn = turn;
|
||||
this.send({ type: "moment", turn });
|
||||
}
|
||||
|
||||
/** Mint a public link to the open moment's turn. */
|
||||
requestShare(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.momentTurn === null) return reject(new Error("no turn open"));
|
||||
this.shareResolve = resolve;
|
||||
this.send({ type: "share", turn: this.momentTurn });
|
||||
setTimeout(() => {
|
||||
if (this.shareResolve === resolve) {
|
||||
this.shareResolve = null;
|
||||
reject(new Error("share timed out"));
|
||||
}
|
||||
}, 10_000);
|
||||
});
|
||||
}
|
||||
|
||||
closeMoment(): void {
|
||||
this.moment = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user