fix: nostr-provider.js 404, DocsPage proxy-unaware URL, round-jump on late viewer join
CI / check (push) Failing after 6m9s
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>
This commit is contained in:
@@ -193,6 +193,52 @@ async function initLiveScene() {
|
||||
scrollLiveLog()
|
||||
}
|
||||
|
||||
// Backfill already-completed rounds when opening a fight already in progress
|
||||
// (e.g. a background poll-mode bot kept fighting while nobody had the viewer
|
||||
// open — the log otherwise starts empty and the NEXT live round is the first
|
||||
// thing to ever appear, reading as "the fight jumped straight to round N").
|
||||
// Deliberately NOT calling handleRoundEnd() for these — that triggers full
|
||||
// scene animation/TTS/fanfare per round, which would replay every missed
|
||||
// round in real time before the viewer could show anything current. This is
|
||||
// a compact, non-animated log backfill only; HP/round-counter state is set
|
||||
// directly from the fetched fight's current values.
|
||||
function backfillCompletedRounds() {
|
||||
const fd = liveFightData.value
|
||||
const roundsData = (fd as any)?.rounds as Array<Record<string, any>> | undefined
|
||||
if (!fd || !fd.botA || !fd.botB || !roundsData?.length) return
|
||||
|
||||
for (const r of roundsData) {
|
||||
const round = r.roundNumber
|
||||
const aWon = r.winnerId === fd.botA.id
|
||||
const bWon = r.winnerId === fd.botB.id
|
||||
const winnerName = aWon ? fd.botA.name : bWon ? fd.botB.name : 'DRAW'
|
||||
liveLogItems.value.push(
|
||||
{ type: 'header', round, text: `ROUND ${round}: ${challengeLabel(r.challengeType)}`, color: 'neon-purple' },
|
||||
)
|
||||
if (r.botAResponse) {
|
||||
liveLogItems.value.push({ type: 'responseA', round, text: `${fd.botA.name}: ${r.botAResponse}`, color: 'neon-cyan' })
|
||||
}
|
||||
if (r.botBResponse) {
|
||||
liveLogItems.value.push({ type: 'responseB', round, text: `${fd.botB.name}: ${r.botBResponse}`, color: 'neon-pink' })
|
||||
}
|
||||
if (r.narration) {
|
||||
liveLogItems.value.push({ type: 'narration', round, text: `>> ${r.narration}`, color: 'neon-yellow' })
|
||||
}
|
||||
liveLogItems.value.push(
|
||||
{ type: 'result', round, text: `${winnerName} ${aWon || bWon ? 'wins round!' : '- no winner'} (${r.botAScore ?? 0} vs ${r.botBScore ?? 0})`, color: aWon ? 'neon-cyan' : bWon ? 'neon-pink' : 'text-muted' },
|
||||
{ type: 'divider', round, text: '', color: '' },
|
||||
)
|
||||
}
|
||||
|
||||
// Reflect current state immediately — don't wait for the next live round
|
||||
// to update HP/round counter away from their initial defaults.
|
||||
const lastRound = roundsData[roundsData.length - 1]
|
||||
liveCurrentRound.value = lastRound.roundNumber
|
||||
if (typeof (fd as any).botAHp === 'number') liveHpA.value = Math.round(((fd as any).botAHp / 200) * 100)
|
||||
if (typeof (fd as any).botBHp === 'number') liveHpB.value = Math.round(((fd as any).botBHp / 200) * 100)
|
||||
scrollLiveLog()
|
||||
}
|
||||
|
||||
// --- SSE event wiring ---
|
||||
// Track in-progress round animation so fight_end can wait for it
|
||||
let _roundEndPromise: Promise<void> | null = null
|
||||
@@ -527,6 +573,10 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
if (isLive.value) {
|
||||
// Show any rounds that already happened before this viewer connected
|
||||
// (see backfillCompletedRounds() for why — a background bot doesn't wait
|
||||
// for a spectator) before wiring the live SSE stream for what's next.
|
||||
if (!isHumanFight.value) backfillCompletedRounds()
|
||||
startPolling({ onFinished: () => stopHumanPolling(), onTimeout: () => stopHumanPolling(), keepLive: isHumanFight.value })
|
||||
wireSSE()
|
||||
// Scene init + human polling are handled by the liveFightData watcher
|
||||
|
||||
Reference in New Issue
Block a user