From 6dc50f5d5d30446a0c1ecd8de1325b30d4e63165 Mon Sep 17 00:00:00 2001 From: Dorian Date: Mon, 9 Mar 2026 15:31:58 +0000 Subject: [PATCH] feat: move creator pubkey to env, fix mobile TTS + signer, button loaders Security: - Move CREATOR_PUBKEY from hardcoded constant to BOTFIGHTS_CREATOR_PUBKEYS env var. Shared isCreatorPubkey() in constants.ts used by auth, admin, tournaments. Frontend checks authorization via API, not client-side. Mobile fixes: - Nostr signer: poll for window.nostr up to 3s (Amber injects late). - TTS: auto-unlock AudioContext on first user interaction via installAutoUnlock() on fight page mount. UX: - Add loading spinners to "I BUILD BOTS" and "I FIGHT MYSELF" buttons. Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/FightViewer.vue | 28 ++++++++++++++++--------- frontend/src/pages/AdminPage.vue | 20 ++++++++++-------- frontend/src/pages/FightPage.vue | 8 ++++++- server/src/lib/constants.ts | 6 ++++++ server/src/routes/admin.ts | 8 ++----- server/src/routes/auth.ts | 18 +++++++--------- server/src/routes/tournaments.ts | 7 +++---- 7 files changed, 55 insertions(+), 40 deletions(-) diff --git a/frontend/src/components/FightViewer.vue b/frontend/src/components/FightViewer.vue index 7bb0cf7..deb165c 100644 --- a/frontend/src/components/FightViewer.vue +++ b/frontend/src/components/FightViewer.vue @@ -306,14 +306,18 @@ async function showHitText(text: string, color: string, x: number) { } function addRoundToLog(round: FightRound, stagger: boolean): Promise { + // Hide human player's typed answer from the battle log + const isAHuman = props.fight.botA?.archetype === 'human' + const isBHuman = props.fight.botB?.archetype === 'human' + if (!stagger) { const challenge = JSON.parse(round.challengeData) logItems.value.push( { type: 'header', round: round.roundNumber, text: `ROUND ${round.roundNumber}: ${challengeLabel(round.challengeType)}`, color: 'neon-purple' }, { type: 'prompt', round: round.roundNumber, text: challenge.displayPrompt || challenge.prompt, color: 'text-muted' }, - { type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' }, - { type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' }, ) + if (!isAHuman) logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' }) + if (!isBHuman) logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' }) if (round.narration) logItems.value.push({ type: 'narration', round: round.roundNumber, text: `>> ${round.narration}`, color: 'neon-yellow' }) const winner = round.winnerId === props.fight.botA?.id ? props.fight.botA?.name : round.winnerId === props.fight.botB?.id ? props.fight.botB?.name : 'DRAW' logItems.value.push({ type: 'result', round: round.roundNumber, text: `${winner} wins round! (${round.botAScore} vs ${round.botBScore})`, color: 'text-secondary' }) @@ -326,14 +330,18 @@ function addRoundToLog(round: FightRound, stagger: boolean): Promise { scrollLog(); await sleep(150) logItems.value.push({ type: 'prompt', round: round.roundNumber, text: challenge.displayPrompt || challenge.prompt, color: 'text-muted' }) scrollLog(); await sleep(200) - logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'}`, color: 'neon-cyan' }) - scrollLog(); await sleep(150) - logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' }) - scrollLog(); await sleep(150) - logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'}`, color: 'neon-pink' }) - scrollLog(); await sleep(150) - logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' }) - scrollLog() + if (!isAHuman) { + logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'}`, color: 'neon-cyan' }) + scrollLog(); await sleep(150) + logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' }) + scrollLog(); await sleep(150) + } + if (!isBHuman) { + logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'}`, color: 'neon-pink' }) + scrollLog(); await sleep(150) + logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' }) + scrollLog() + } })() } diff --git a/frontend/src/pages/AdminPage.vue b/frontend/src/pages/AdminPage.vue index c304464..65069fd 100644 --- a/frontend/src/pages/AdminPage.vue +++ b/frontend/src/pages/AdminPage.vue @@ -1,10 +1,8 @@