From eb0cf267a533ea3f030af658d567ec27761c10fe Mon Sep 17 00:00:00 2001 From: Chris Pardy Date: Wed, 17 Jun 2026 08:06:22 -0400 Subject: [PATCH] Play-test interest: link to group chat instead of writing a record The interest page no longer writes a town.deception.interest PDS record; it links the player to the play-testing group chat. The Bluesky DM URL is injected at build time from VITE_PLAYTEST_CHAT_URL (client/.env, gitignored; ships to the Fly builder since .dockerignore's `.env` only matches root). - PlaytestInterest.tsx: drop the POST /api/interest flow; render a "join the play-testing group" link (fails soft if the URL is unset). - Remove the now-dead /api/interest route, persist.writeInterest, and the repo:town.deception.interest OAuth scope (no longer written). - vite-env.d.ts: type VITE_PLAYTEST_CHAT_URL. Co-Authored-By: Claude Opus 4.8 (1M context) --- client/src/PlaytestInterest.tsx | 61 ++++++++++----------------------- client/src/vite-env.d.ts | 9 +++++ server/src/config.ts | 6 ++-- server/src/index.ts | 20 ++--------- server/src/oauth.ts | 1 - server/src/persist.ts | 12 ------- 6 files changed, 34 insertions(+), 75 deletions(-) diff --git a/client/src/PlaytestInterest.tsx b/client/src/PlaytestInterest.tsx index 33e6fab..c676c1c 100644 --- a/client/src/PlaytestInterest.tsx +++ b/client/src/PlaytestInterest.tsx @@ -1,33 +1,12 @@ // Shown on the Deception takeover's back page while play-test mode is on (SABOTAGE_PLAYTEST), in -// place of the map chooser. Deception isn't open yet — instead of starting a game, the player can -// register interest, which writes an (empty) town.deception.interest record to their repo via the -// server. Same light/paper theme as MapSelector. The back button returns to the mode picker. -import { useState } from 'react' +// place of the map chooser. Deception isn't open yet — instead of starting a game, the player is +// pointed at the play-testing group chat (a Bluesky DM, URL injected at build time via +// VITE_PLAYTEST_CHAT_URL / client/.env). No PDS write happens. Same light/paper theme as MapSelector; +// the back button returns to the mode picker. -type Status = 'idle' | 'sending' | 'done' | 'error' +const CHAT_URL = import.meta.env.VITE_PLAYTEST_CHAT_URL export function PlaytestInterest({ onBack }: { onBack: () => void }) { - const [status, setStatus] = useState('idle') - const [error, setError] = useState(null) - - async function register() { - setStatus('sending') - setError(null) - try { - const res = await fetch('/api/interest', { method: 'POST', credentials: 'include' }) - const data = await res.json().catch(() => ({})) - if (!res.ok) { - setError(data.error ?? `error ${res.status}`) - setStatus('error') - return - } - setStatus('done') - } catch (e) { - setError(e instanceof Error ? e.message : String(e)) - setStatus('error') - } - } - return (
- )} - - {status === 'error' && ( -

Couldn't register interest: {error}

+ join the play-testing group → + + ) : ( + // VITE_PLAYTEST_CHAT_URL wasn't set at build time — fail soft rather than render a dead link. +

+ The group-chat link isn't configured yet — check back soon. +

)}
diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts index 11f02fe..4cbe555 100644 --- a/client/src/vite-env.d.ts +++ b/client/src/vite-env.d.ts @@ -1 +1,10 @@ /// + +interface ImportMetaEnv { + /** Bluesky group-chat invite the play-test interest page links to (build-time, from client/.env). */ + readonly VITE_PLAYTEST_CHAT_URL?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/server/src/config.ts b/server/src/config.ts index e2b9c55..da5e715 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -21,9 +21,9 @@ export const DEV_SOLO = process.env.SABOTAGE_DEV_SOLO === '1' || process.env.SAB /** * Play-test interest mode (`SABOTAGE_PLAYTEST=1`). When on, the Deception case opens an "I'm - * interested in play-testing" page instead of the map chooser: tapping it writes an (empty) - * town.deception.interest record to the player's repo rather than starting a game. Used while the - * mode is gated to gauge demand. Surfaced to the client via /api/session so the SPA can swap pages. + * interested in play-testing" page instead of the map chooser: it links the player to the + * play-testing group chat (client-side, VITE_PLAYTEST_CHAT_URL) rather than starting a game — no PDS + * write. Used while the mode is gated. Surfaced to the client via /api/session so the SPA can swap pages. */ export const PLAYTEST = process.env.SABOTAGE_PLAYTEST === '1' || process.env.SABOTAGE_PLAYTEST === 'true' diff --git a/server/src/index.ts b/server/src/index.ts index ff95f99..94a83cf 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -44,7 +44,7 @@ import { SID_COOKIE, appUrl, } from './oauth.js' -import { writeGameDeclaration, writeResults, writeInterest, copyDefaultSprite } from './persist.js' +import { writeGameDeclaration, writeResults, copyDefaultSprite } from './persist.js' import { startJetstream } from './jetstream.js' import { resolveGameWorld } from './maps.js' import { resolveSprite } from './atproto.js' @@ -237,22 +237,8 @@ app.get('/api/rooms/:code', (c) => { }) }) -// Register play-test interest: write an empty town.deception.interest record to the authenticated -// caller's repo on their behalf. Only available while play-test mode is on (the gate that swaps the -// map chooser for the interest page); otherwise there's no interest page to call it. -app.post('/api/interest', async (c) => { - if (!PLAYTEST) return c.json({ error: 'not-found' }, 404) - const did = didFromCookieHeader(c.req.header('cookie')) - if (!did) return c.json({ error: 'unauthorized' }, 401) - try { - const agent = await agentForDid(did) - const uri = await writeInterest(agent, did) - return c.json({ uri }) - } catch (err) { - console.error('[interest] write failed:', err instanceof Error ? err.message : err) - return c.json({ error: 'pds-write-failed', message: err instanceof Error ? err.message : String(err) }, 502) - } -}) +// Play-test interest is no longer a PDS write: the interest page just links the player to the +// play-testing group chat (client-side, VITE_PLAYTEST_CHAT_URL). Nothing to handle server-side. // --- single-player deduction ------------------------------------------------ diff --git a/server/src/oauth.ts b/server/src/oauth.ts index 178da42..47c467f 100644 --- a/server/src/oauth.ts +++ b/server/src/oauth.ts @@ -43,7 +43,6 @@ const BASE_SCOPE = [ 'atproto', 'repo:town.deception.game', // host's game declaration 'repo:town.deception.game.result', // each player's signed round result - 'repo:town.deception.interest', // player interest record 'repo:actor.rpg.stat', // win tracking (rkey deception.town, enforced server-side) ] // The sprite-write scope is requested conditionally: only a player who has NO actor.rpg.sprite/self diff --git a/server/src/persist.ts b/server/src/persist.ts index 21f91ed..7aae0ec 100644 --- a/server/src/persist.ts +++ b/server/src/persist.ts @@ -17,7 +17,6 @@ import type { GameDeclaration } from '../../engine/lobby.js' const GAME_NSID = 'town.deception.game' const RESULT_NSID = 'town.deception.game.result' -const INTEREST_NSID = 'town.deception.interest' // The deduction win record (DEDUCTION.md). A single self-keyed actor.rpg.stat per player tallies // wins across all deduction games; the rkey scopes it to this mode within the town namespace. @@ -49,17 +48,6 @@ export async function recordDeductionWin(did: Did): Promise { } } -/** - * Record a player's play-test interest: an empty town.deception.interest record (just $type + - * createdAt) in their own repo. The rkey is a server-minted TID (createRecord's default), so a - * player can register interest more than once. Returns the new record's AT-URI. - */ -export async function writeInterest(agent: Agent, did: Did): Promise { - const record = { $type: INTEREST_NSID, createdAt: new Date().toISOString() } - const res = await agent.com.atproto.repo.createRecord({ repo: did, collection: INTEREST_NSID, record }) - return res.data.uri -} - /** A blob reference as it appears in / is written back to a record's JSON. */ interface BlobRef { $type?: 'blob' -- 2.54.0