Commit Graph
195 Commits
Author SHA1 Message Date
DorianandClaude Fable 5 877d1f6389 fix: broken profile images (CSP img-src), AI-answer discoverability, arena compose tag
CI / check (push) Failing after 6m10s
1. server/src/app.ts: CSP img-src only allowed 'self'/data:/blob: — nostr
   profile pictures come from kind:0 metadata, a URL the user sets via their
   own client, hosted on whatever domain they picked. There's no central
   image host for a decentralized identity system, so every external
   profile pic was CSP-blocked and rendered as a broken image. Added
   https: (broad) — safe here since images can't execute script even from
   an untrusted origin, unlike script-src which stays locked to 'self'.

2. frontend/src/pages/JoinBoutPage.vue: the new AI-answer option (1.2.7)
   was reported as invisible — it was gated behind picking POLLING (not the
   default WEBHOOK) AND behind a collapsed toggle within that. Changed:
   POLLING is now the default mode (also the documented default in
   BOTFIGHTS.md), the AI section is expanded by default instead of
   collapsed, and the POLLING button's own description now mentions the
   option so it's visible without any extra click.

3. docker-compose.arena.yml: image tag 1.2.1 -> 1.2.7, matching what's
   actually deployed on the canonical arena (rolled live via
   ssh+docker compose pull/up this session — this commit just brings the
   repo's copy of the compose file back in sync with reality).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 08:52:21 -04:00
DorianandClaude Fable 5 ca5b63468e feat: "let BotFights answer for me" — server-side AI bot (poll mode), + Latest Bouts short-viewport fix
CI / check (push) Failing after 6m8s
New feature, requested live during demo prep: an operator can paste their
own Anthropic or OpenAI API key and have the server itself answer fight
challenges for their poll-mode bot, instead of running an external script.

Storage (server/src/engine/ai-bot-config.ts): one 0600 JSON file per bot
under this app's own data volume — deliberately mirrors Archipelago's own
node-level pattern for the identical class of secret (system.settings.set
"claude_api_key" in core/archipelago/src/api/rpc/system/handlers.rs): never
returns the raw key on GET, only whether one is configured and which
provider. This is a human operator opting in for their own bot via the
app's own UI — a different trust boundary from the unified prompt's "never
ask an AI agent for its API key" rule (BOTFIGHTS.md), not a violation of it.

Execution (server/src/engine/orchestrator.ts): purely additive hook inside
the existing isPollingBot branch of getBotResponse(). waitForPollResponse()
(unchanged) registers the pending challenge synchronously before returning;
right after, answerWithAiIfConfigured() fires a fire-and-forget async LLM
call that races to call submitPollResponse() — the exact function an
external poller already calls — before that promise's own timeout. No AI
config = instant no-op. LLM error/timeout = the existing timeout path
handles it identically to a human forgetting to poll. No new failure mode,
no change to scoring/round timing for any other bot.

Adapter (server/src/engine/llm-adapter.ts): both providers, one call shape
each (Anthropic /v1/messages + x-api-key, OpenAI /v1/chat/completions +
Bearer), same competitive system prompt already documented in BOTFIGHTS.md
for operator-run bots.

API (server/src/routes/bots.ts): POST/GET/DELETE /api/bots/ai-config,
authenticated via the bot's own Authorization: Bot <id>:<secret> (same as
/api/fights/poll). Registered BEFORE GET /:name — same route-shadowing bug
class already found once in fights.ts's /poll route (09-05); a bare /:name
registered first would have swallowed /ai-config as a bot-name lookup.
Covered by a new test (ai-config.test.ts) that asserts the real contract
shape, not just a 200, specifically to catch that regression.

UI (frontend/src/pages/JoinBoutPage.vue): collapsible section in the
bot-setup step, poll mode only (webhook mode already assumes the operator
runs their own infra) — provider picker, password-type key input, links to
get a key from either provider, key cleared from page state immediately
after saving.

Explicitly deferred (not built here): "use this node's key" as an
alternative to pasting your own — the node already has one configured for
AIUI (found live, /opt/archipelago/claude-api-proxy.py), but wiring a
cross-container secret share from the archy orchestrator into this
container needs a manifest change and another catalog signing cycle, which
this session isn't improvising under demo time pressure.

Also: HomePage.vue "Latest Bouts" section hidden on short viewports
([@media(max-height:700px)]:hidden) — the hero layout is a vertically-
centered flex column with overflow-hidden and no scroll by design, so on a
short viewport (embedded node dashboard iframes, small kiosk screens) this
last/least-essential section was what silently clipped, reported live as
"it looks cut off on node screens often".

Fixed a regression-test violation this batch would have introduced
(BUG-F2: no silent .catch(() => {})) in the DocsPage.vue proxy-URL-resolve
fix from the previous commit — both catches now log a warning instead of
swallowing silently.

Verified: full server typecheck clean; orchestrator.test.ts (23) +
poll-responses.test.ts (10) unchanged and passing — the new hook doesn't
alter existing poll-mode behavior; new ai-config.test.ts (7) passing,
including the route-shadowing regression check; regression.test.ts (43,
including the newly-fixed BUG-F2) passing. lifecycle.test.ts/scoring.test.ts
perf-timing failures are pre-existing, documented, unrelated flakiness
under this shared machine's CPU load (see archy's 09-01 deferred-items.md
item 2) — not caused by this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 08:06:02 -04:00
DorianandClaude Fable 5 2512265113 fix: nostr-provider.js 404, DocsPage proxy-unaware URL, round-jump on late viewer join
CI / check (push) Failing after 6m9s
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>
2026-07-31 07:16:24 -04:00
DorianandClaude Fable 5 2c039f2af3 fix(nostr): reactive extension detection + native Archipelago signer bridge
CI / check (push) Failing after 6m19s
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>
2026-07-31 06:58:53 -04:00
DorianandClaude Fable 5 ffd4dfd25f fix(setup-guide): stop leaking proxy-mode local addresses into the AI setup prompt
CI / check (push) Failing after 6m17s
JoinBoutPage.vue and BotProfilePage.vue's "copy AI setup guide" flows both
fetched the static /docs/BOTFIGHTS.md file and substituted {{ARENA_URL}}
client-side with window.location.origin. On an instance running in proxy
mode (ARENA_UPSTREAM_URL set), that substitutes whatever address the
browser happens to be on — e.g. this node's own LAN/Tailscale IP — into a
guide meant to be handed to an external AI agent, which then can't reach
that address at all (private/overlay network, no route from outside).

Root cause: the static file bypasses arena-proxy entirely (it only mounts
on /api/*), so the substitution had no way to know about proxy mode.

Fix: both flows now fetch the server-rendered /api/docs/prompt instead.
That route is under /api/*, so arena-proxy transparently forwards it to
the real upstream arena in proxy mode, which resolves {{ARENA_URL}} to its
own correct, externally-reachable origin (server/src/routes/docs.ts,
unchanged, already correct) — same fix class as the JoinBoutPage
mode-picker guide-banner change (603e09b), same root cause discovered via
a live demo incident (a bot got a Tailscale address in its setup guide and
correctly refused to act on it).

Known follow-up, not fixed here: DocsPage.vue's `promptUrl` display link
(`${window.location.origin}/api/docs/prompt`) has the same class of issue
for the *link itself* (not its content) — lower risk, deferred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 06:37:55 -04:00
DorianandClaude Fable 5 603e09b6d8 fix(join-bout): make the setup-guide mode picker visibly do something
CI / check (push) Failing after 6m10s
BOT-02 consolidated the poll/webhook setup docs into one file
(BOTFIGHTS.md) that documents both options — correct by design, but the
UI still had a leftover watch(connectionMode, ...) that cleared and
refetched the *same* file every time you clicked WEBHOOK/POLLING, showing
a "Loading..." flash for content that never actually changed. From a
user's perspective the tab looked broken: click it, nothing visibly
different happens.

Fix: stop the pointless refetch, and add a `modeHint()` banner — colored
per mode, updates instantly on click — inside the guide viewer and
prepended to the copied text, pointing the reader/AI at "Option A: Polling
Bot" or "Option B: Webhook Bot" within the same doc. The mode picker now
produces an immediate, obvious visual change, and the copied prompt is
mode-aware even though the underlying file is shared by design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 06:02:42 -04:00
DorianandClaude Fable 5 d2fc998a28 docs(prompt): AI-agent consent preamble, operator-only LLM creds (drop ANTHROPIC_API_KEY pattern), accurate queue-join timing — feedback from first real AI agent test
CI / check (push) Failing after 6m5s
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>
2026-07-30 23:52:18 -04:00
DorianandClaude Fable 5 2a343ac746 feat(09-02): retire the bare-pubkey session path (BOT-01)
CI / check (push) Failing after 6m11s
Client: useNostr.ts's auto-restore now calls GET /api/auth/me (a plain
authFetch, no body) instead of POSTing {pubkey} to /api/auth/login —
identity is derived server-side from the JWT alone, never claimed by
the client.

Server: POST /login is reduced to a pure, documented-deprecated read.
Removed the creator auto-create branch and the creator auto-upgrade
db.update block — an unauthenticated request can no longer mutate the
database via this endpoint. The identical creator auto-create/upgrade
logic already exists, correctly gated behind NIP-98 verification, in
POST /nostr/session, so a creator signing in with a real signer still
gets the same row created/upgraded. Added a handler doc comment plus a
new auth.test.ts case asserting an unregistered creator pubkey now
returns exists:false and leaves the bots table row count unchanged.

e2e/helpers/auth.ts: doc comments updated to describe loginWithPubkey
as a read-only test lookup helper, not a login; request/signature
unchanged so existing e2e specs keep working.

Verification: auth.test.ts + auth-edge.test.ts + auth-audit.test.ts +
auth-me.test.ts = 56/56 pass. Full server suite (bypassing pnpm's
install-gate via ./node_modules/.bin/vitest, since this environment's
pnpm needs an interactive build-approval step unrelated to this task)
= 810/817 pass, remaining 7 are pre-existing timing/perf flakes under
CPU load (lifecycle/speed-meta/tier-balance/bot-auth constant-time),
none touching auth. tsc (server) and vue-tsc (frontend) both exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 22:31:26 -04:00
Dorian 2dd9947516 feat(09-03): point every in-app setup surface at the one prompt, add copy affordances
CI / check (push) Failing after 6m7s
- JoinBoutPage.vue: setupDocPath()/setupDocName() collapse to /docs/BOTFIGHTS.md
  regardless of connection mode; keep YOUR_BOT_ID/YOUR_BOT_SECRET substitution
  and add a third substitution for {{ARENA_URL}} -> window.location.origin
- BotProfilePage.vue: same collapse; the webhook/polling guide-type selector
  is gone (there's only one guide now) -- replaced with a single 'LOAD SETUP
  GUIDE' button, same credential + {{ARENA_URL}} substitution
- DocsPage.vue: new 'GIVE THIS TO YOUR AI' panel at the top of the page --
  copy-to-clipboard for the full /api/docs/prompt response, plus the literal
  URL shown so a user can hand an agent the link instead of the text.
  Existing API reference / webhook tester tabs unchanged below it
- e2e/signup-bot.spec.ts: two new tests -- docs page shows the copy
  affordance, and GET /api/docs/prompt (via page.request) returns 200 with
  the registration endpoint and no leftover {{ARENA_URL}} token
2026-07-30 22:24:48 -04:00
Dorian bbc3c7acff docs(09-03): consolidate BOTFIGHTS setup docs into one self-contained AI prompt
CI / check (push) Failing after 6m13s
- 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)
2026-07-30 22:01:43 -04:00
DorianandClaude Fable 5 cfafc22c62 fix(frontend): clipboard polyfill for HTTP (non-secure) contexts — copy buttons threw writeText-of-undefined on nodes
CI / check (push) Failing after 6m9s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 21:46:17 -04:00
DorianandClaude Opus 4.6 fb35075b01 fix: arcade mode crash — kaplay go() defers scene init to next frame
CI / check (push) Failing after 7m34s
k.go('arcade') schedules the scene callback on frameEnd, not synchronously.
start() was called before fighters existed, causing "Cannot read properties
of undefined (reading 'obj')". Fix: await a readiness promise that resolves
once the scene callback has created the fighters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 20:17:10 +01:00
Dorian 32e6c19f72 stuff 2026-04-11 19:46:37 +01:00
DorianandClaude Opus 4.6 ebf6667f8f fix: wire SSE for bot fights on mount — rounds weren't displayed in practice mode
wireSSE() was only called for human fights in onMounted, so bot fights
(webhook/poll mode) never received round_start/round_end events. The fight
ran server-side but the frontend showed nothing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:56:08 +00:00
DorianandClaude Opus 4.6 42b1642932 fix: register bot at name confirmation, show credentials in setup step
- Bot registered immediately after name check (poll mode default)
- bot_id + secret available in bot-setup step with COPY GUIDE + CREDENTIALS
- Webhook mode updates URL on existing bot instead of re-registering
- Collapsible guide preview with credentials injected
- Resets guide content when switching webhook/polling mode
- Fix secret text overflow with break-all on profile page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:36:18 +00:00
DorianandClaude Opus 4.6 d17f6970b9 fix: move setup guide to post-registration step where credentials exist
- Remove setup guide from pre-registration bot-setup step (no credentials yet)
- Add collapsible guide preview to ready step with real bot_id/secret injected
- "COPY GUIDE + CREDENTIALS" copies the full guide with credentials embedded

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:15:11 +00:00
DorianandClaude Opus 4.6 2e7a039b8b fix: serve /docs/*.md as static files, not SPA fallback
- 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>
2026-03-13 16:16:44 +00:00
DorianandClaude Opus 4.6 60dd6893be fix: replace download with inline copy-paste setup guide, fix CSP for WASM + Nostr relays
- Replace file download with collapsible inline guide + COPY ALL button
- Guide content has bot_id/secret pre-filled from credentials
- JoinBoutPage: "COPY GUIDE + CREDENTIALS" eagerly loads guide content
- BotProfilePage: webhook/polling guide selector with copy after secret regen
- CSP: add wasm-unsafe-eval to scriptSrc (fixes Kokoro TTS WASM)
- CSP: add wss://relay.damus.io, wss://relay.nostr.band, wss://nos.lol to connectSrc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:06:34 +00:00
DorianandClaude Opus 4.6 cef9f4188f feat: fix setup guide download with embedded credentials + profile page regenerate secret
- downloadSetupGuide() now triggers a real file download with bot_id/secret injected
- Add POST /api/auth/regenerate-secret endpoint (JWT auth, 3/hour rate limit)
- Add "Download Setup Guide" section to BotProfilePage with secret regeneration flow
- Old secret immediately invalidated on regeneration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:35:48 +00:00
DorianandClaude Opus 4.6 08437cdf5c test: add static analysis tests for memory leak destroy contracts
Verifies FightScene.destroy(), FightViewer.vue onUnmounted, and audio
module cleanup invariants via source-code scanning — ensures future
changes don't silently break resource cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:06:42 +00:00
DorianandClaude Opus 4.6 854b1cd1df fix: prevent memory leaks on consecutive fight replays
- FightViewer: store canvas event handlers and remove them before
  replacing canvas elements, preventing detached DOM/closure leaks
- FightViewer: clean up canvas listeners on unmount
- tts.ts: clear _staticLoading dedup map after precache completes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:04:03 +00:00
DorianandClaude Opus 4.6 aa263ec8ae test: verify TTS cache uses LRU eviction, not FIFO (BUG-8)
Cache already uses lastAccess timestamps and LRU eviction. Added
test-only exports and 6 tests verifying: timestamp tracking, access
updates, LRU eviction of oldest entry, recently accessed entries
survive eviction, and eviction is not FIFO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:00:59 +00:00
DorianandClaude Opus 4.6 8afdc2d898 fix: replace raw setTimeout with tracked timers in game audio (BUG-4)
Added audioTimeout() to audio/context.ts — tracked timer set cleared on
scene destroy via clearAllAudioTimers(). Converted 20 sfx.ts + 3 voice.ts
raw setTimeout calls to audioTimeout. Converted FightScene playEntrance
timeout to trackedTimeout. Remaining setTimeout in tts.ts worker layer
and music.ts (already tracked via setMusicTimeout) are self-managing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:59:16 +00:00
DorianandClaude Opus 4.6 55d0f84251 chore: pin all dependency versions for reproducible builds
Remove ^ prefix from all 33 dependencies across root, server, and
frontend package.json files. Lockfile regenerated and verified with
pnpm install --frozen-lockfile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 06:21:16 +00:00
DorianandClaude Opus 4.6 abc081487c fix: pass archetype/customization to human morph sprite generation (BUG-10)
Human morph sprites were generated without archetype and customization
params, causing fallback to generic sprites instead of the bot's actual
appearance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 05:23:53 +00:00
DorianandClaude Opus 4.6 7698d560d6 fix: webhook verify shows loading spinner and better error on failure
Added isVerifyingWebhook state with spinner and disabled button during
webhook test. Improved error message to guide user on retry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 05:20:14 +00:00
DorianandClaude Opus 4.6 642da1e477 fix: polling backoff escalates on errors — 1.5s → 3s → 6s → 8s max
Changed from fixed 1.5s setInterval to recursive setTimeout with
exponential backoff on consecutive errors. Resets to 1.5s on success.
Added 2 tests verifying backoff escalation and reset behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 05:18:00 +00:00
DorianandClaude Opus 4.6 8b72bef22e fix: query all 3 Nostr relays in parallel, pick latest profile (BUG-F8)
fetchNostrProfile now uses Promise.allSettled to query all relays
concurrently. Aggregates results with latest-created_at-wins strategy
instead of stopping at the first relay that responds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:10:11 +00:00
DorianandClaude Opus 4.6 c288b23c13 fix: persist autoRestoreRan on globalThis to survive Vite HMR (BUG-F7)
Module re-evaluation during HMR reset autoRestoreRan to false, causing
duplicate auth-restore API calls. Now persists flag on globalThis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:09:20 +00:00
DorianandClaude Opus 4.6 1c296c6f1c test: add frontend composable tests and remaining test files
useNostr (9), useFightCache (5), useOnlineStatus (4) composable tests.
Added fake-indexeddb dev dependency for IDB tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:08:01 +00:00
DorianandClaude Opus 4.6 004413457d fix: use 'invoiced' status on NWC fallback instead of invalid 'pending'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:15:49 +00:00
DorianandClaude Opus 4.6 e7d3cab85a feat: add ErrorBoundary component with onErrorCaptured (BUG-F5)
Catches runtime errors in child components, displays user-friendly
error message with reload button. Wired into App.vue wrapping
router-view. Tests verify error capture and button rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:15:28 +00:00
DorianandClaude Opus 4.6 74cb5cc728 fix: NWC payment timeout/error now rejects properly (BUG-F4)
- Timeout and WebSocket errors reject with proper Error objects
- Caller catches and falls through to poll-based confirmation
- Preimage undefined check prevents calling confirm with no preimage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:10:50 +00:00
DorianandClaude Opus 4.6 acecc79d04 test: verify HumanFightPage timer cleanup on unmount (BUG-F3)
feedbackTimer, timerHandle, and pollHandle are all cleared in
onUnmounted. Test confirms cleanup pattern works correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:07:47 +00:00
DorianandClaude Opus 4.6 b4900cb66f fix: replace all silent .catch(() => {}) with console.warn (BUG-F2)
13 silent catch handlers replaced with descriptive console.warn logging
across 6 frontend files. No silent error swallowing remains.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:06:22 +00:00
DorianandClaude Opus 4.6 e5ed856df9 fix: SSE reconnection with exponential backoff (BUG-F1)
SSE now always attempts reconnection when fight isn't finished,
regardless of isLive.value. Uses exponential backoff (1s, 2s, 4s,
max 8s). Moved sseRetries to outer scope to persist across reconnects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:02:10 +00:00
DorianandClaude Opus 4.6 d9e32123fe test: add test infrastructure for frontend and server
- Frontend: vitest.config.ts with vue plugin + jsdom, dummy component test
- Server: in-memory SQLite test DB factory + Hono testClient helper + smoke test
- CI: add pnpm audit and server coverage threshold steps
- Root: vitest workspace config for multi-project test discovery

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:30:43 +00:00
DorianandClaude Opus 4.6 6f0eb92ebb fix: both-wrong draws + double TTS entrance fix
Scoring: both-wrong is now a DRAW — equal scores (3/3), no winner,
symmetric minimal damage. Garbage answers no longer beat reasonable
ones just by being faster. Both-wrong narrations reflect the draw.

Entrance: removed duplicate announceDeepIntro() call from FightViewer
(was already called inside playEntrance). Removed _resetPositions()
after entrance (entrance already places fighters at home positions,
the extra reset caused a visible snap/reset).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:09:11 +00:00
DorianandClaude Opus 4.6 017d0e3e4c fix: consolidate bot setup into single step with doc download + mode picker
Merged bot-setup and choose-connection into one actionable step.
Users now see mode picker, download button for the correct guide
(BOTFIGHTS-WEBHOOK.md or BOTFIGHTS-POLLING.md), and safety info
all on one screen instead of two filler steps with no actions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:02:45 +00:00
DorianandClaude Opus 4.6 af50580aca fix: generated identity uses extension signer instead of local key
buildNip98Token checked window.nostr before secretKeyHex, so
"Generate New Identity" would sign with the browser extension's key
after saving. Now explicit keys always take priority over extensions.

Also made setup flow mode-aware: webhook users get BOTFIGHTS-WEBHOOK.md,
polling users get BOTFIGHTS-POLLING.md with matching copy prompts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:49:22 +00:00
Dorian bc4a52bc12 fixes 2026-03-12 16:35:59 +00:00
Dorian 45216e5dfc second human fight fixes 2026-03-11 10:58:03 +00:00
Dorian ea72c097c4 human fight sequence fix 2026-03-11 10:34:08 +00:00
Dorian 29a0a48eb1 human fight sequence fix 2026-03-11 10:02:37 +00:00
Dorian 974566778e characters invisible on human vs bot fight entrance fix 2026-03-11 09:17:20 +00:00
Dorian bbe656929c another fix for human choices 2026-03-11 00:13:31 +00:00
Dorian 112bcde515 human fight non multiple choice fix 2026-03-10 23:26:36 +00:00
DorianandClaude Opus 4.6 95ed80335a feat: polling API, HMAC webhook signing, session-only keys, prod audio fix
- Add polling API (GET/POST /api/fights/poll) so bots don't need public URLs
- Add HMAC-SHA256 webhook signing (X-Botfights-Signature header)
- Stop auto-persisting nsec keys — session-only by default with opt-in "Remember on this device"
- Fix production TTS: add wav/mp3/ogg MIME types, /audio/* route, SPA blocklist
- Overhaul docs: mode selector (poll vs webhook), AI-first bot examples, security tab
- Fix duplicate sign-in buttons, login flow bugs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:34:22 +00:00
DorianandClaude Opus 4.6 150ce7447d fix: human vs AI fight bugs — CSP for TTS, invisible sprites, fight end sequence
- Allow huggingface.co in CSP connect-src (fixes Kokoro TTS model download)
- Add registerSW.js route (fixes PWA service worker 404)
- Add _resetPositions() safety after entrance (fixes invisible fighters)
- Fight end sequence works without canvas scene (KO/overlays/log always play)
- Pre-fight instructions in battle log for human players
- NIP-55 visibility sync and cleanup handlers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:00:20 +00:00
DorianandClaude Opus 4.6 63cc00fcb6 feat: add NIP-55 Android signer support for Amber/Primal login
When window.nostr isn't available (common on mobile Chrome where
extensions can't inject), fall back to NIP-55 nostrsigner: intent
URIs. This opens Amber/Primal directly to sign a NIP-98 event,
then redirects back with the signed event for JWT authentication.

- Build nostrsigner: URI with unsigned NIP-98 event + callback URL
- Process NIP-55 callback on page mount (extract signed event from URL)
- Auto-detect Android to show "SIGN IN WITH AMBER / PRIMAL" label
- Reduced window.nostr polling from 3s to 2s before NIP-55 fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:15:16 +00:00