Three more fixes found during live demo verification:
1. server/src/app.ts: the previous commit added <script src="/nostr-provider.js">
to index.html and shipped the file into server/public/, but this app's
static file serving is an explicit per-route allowlist, not a catch-all —
there was no route registered for it, so it 404'd and the signer bridge
silently never loaded. Added the missing app.get('/nostr-provider.js', ...)
route.
2. DocsPage.vue: promptUrl (the displayed "give this URL to your AI" copy
button) was built from window.location.origin — same root-cause bug class
as the JoinBoutPage/BotProfilePage fix (ffd4dfd), just for a link instead
of fetched content. Now resolves the real arena origin from the fetched
prompt's own content (which IS correctly proxy-resolved server-side via
arena-proxy) instead of the browser's current address.
3. FightPage.vue: opening a fight already in progress (e.g. a background
poll-mode bot kept answering challenges while nobody had the viewer open)
showed nothing until the next live round arrived — reads as "the fight
jumped straight to round N". loadFight() always fetched the completed
rounds (data.rounds) but nothing backfilled the visible log from them;
only live SSE round_end events ever pushed into liveLogItems. Added
backfillCompletedRounds(), called once on mount before wireSSE() connects,
that renders a compact (non-animated — no scene/TTS replay) summary of
every already-completed round and sets HP/round-counter to current state
immediately.
4. BOTFIGHTS.md: documented the webhook_test signature exception (see ffd4dfd
commit for the same fix already applied to the live doc endpoint's
underlying example) — this file is frontend/public/docs/BOTFIGHTS.md,
the static copy that predates today's /api/docs/prompt-only rendering
fix; keeping both in sync since some flows may still reference the path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes found during live signer-login verification:
1. hasExtension was `computed(() => !!window.nostr)` — window.nostr is a
plain global with no Vue reactivity, so this evaluated once, lazily, on
first read and cached forever. If the extension's content script hadn't
injected yet at that moment (common — extensions often inject slightly
after page scripts start), "SIGN IN WITH EXTENSION" disappeared
permanently, even once the extension finished injecting moments later.
Reported live as "no browser extension or signer option ever shows".
Fixed: hasExtension is now backed by a real ref, seeded from the current
value and upgraded by a short poll (existing waitForSigner() precedent,
same 200ms/timeout shape) so the UI reacts when the extension actually
appears.
2. Added Archipelago's native NIP-07 signer bridge (frontend/public/
nostr-provider.js, copied verbatim from neode-ui/public/nostr-provider.js
— the canonical source) via a <script> tag in index.html. This no-ops
immediately outside an iframe (window === window.top), so a real browser
extension in a standalone tab is unaffected. Inside the Archipelago node
dashboard's iframe, it provides window.nostr backed by the node's own
identity via postMessage to
neode-ui/src/views/appSession/useNostrBridge.ts (already generic — no
per-app allowlist needed for the getPublicKey/signEvent bridge itself,
only for the optional auto-login/identity-picker convenience flow, which
this app doesn't use). Existing login() flow (buildNip98Token ->
POST /api/auth/nostr/session) works unchanged through this bridge.
Together: signing in now works reliably both in the dashboard iframe (no
extension needed at all) and in a direct tab (real extension, now reliably
detected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first cloud AI agent given the prompt refused it as a suspected prompt
injection: 'nothing else needs to be read' framing, raw-IP fallback, and
being told to wire its own ANTHROPIC_API_KEY into a persistent script. All
three patterns removed; brains are now an operator-supplied optional
OpenAI-compatible endpoint with local heuristics as default. queue/join
documented as blocking ~35s (60s client timeout) matching the real 30s
production QUEUE_TIMEOUT_MS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Merge BOTFIGHTS.md + BOTFIGHTS-EASY/POLLING/WEBHOOK.md + BOT_SETUP.md into
a single canonical prompt at frontend/public/docs/BOTFIGHTS.md
- Add the previously-undocumented registration step (POST /api/bots,
anonymous, poll vs webhook mode, rate limits, 409/422 behavior)
- Replace the stale botfights.io fallback host with the {{ARENA_URL}} token
(substituted server-side/client-side in later tasks)
- Document the exact HMAC-SHA256 webhook signature derivation, matching
orchestrator.ts (the old bot.js example's verifySignature() was wrong —
it hashed BOT_SECRET directly instead of via the secretHash+signingKey
two-step the server actually uses)
- Document the trash_talk (webhook, snake_case) vs trashTalk (poll,
camelCase) field-naming split, verified against the real zod schemas
- Add the endpoint reference table, troubleshooting table, and arena-as-relay
framing (any node can host an arena; default is the Foundation's)
- Replace root BOTFIGHTS.md with a 4-line stub pointing at the canonical copy
- Delete the four superseded docs (BOTFIGHTS-EASY/POLLING/WEBHOOK.md, BOT_SETUP.md)
- Add /docs/* route to serve markdown setup guides
- Add md to MIME map and SPA catch-all exclusion
- Normalize BOTFIGHTS.md placeholders to YOUR_BOT_ID/YOUR_BOT_SECRET
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
71 static voice files (9.6MB) for round calls, intros, hype lines,
and challenge announces. These play instantly from file instead of
running Kokoro TTS generation in the browser. Dynamic content
(questions, answers, narrations) still uses Kokoro.
Includes Node.js generation script (scripts/generate-voice-files.mjs)
and modified tts.ts to check static file cache before worker generation.
Static audio works even before Kokoro model finishes loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add vite-plugin-pwa with autoUpdate service worker
- Generate SVG + PNG icons (192, 512, apple-touch-icon, favicon)
- Web app manifest with standalone display mode
- Workbox caching: precache assets, cache Google Fonts, NetworkFirst for API
- Apple mobile web app meta tags for iOS home screen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>