fix: page-breaking bugs — duplicate onMounted, SSE race, canvas lifecycle, ghost animation
- ArenaPage: merge duplicate onMounted hooks into single parallel fetch - FightPage: queue SSE events until liveFightData ready, add reconnect with backoff - FightViewer: fix stale canvas ref with container ref, add sceneReady guard - HomePage: replace stopCycling boolean with AbortController for clean unmount Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
84625bc4d2
commit
e31b15ed28
@@ -64,6 +64,7 @@ const liveAnnouncementColor = ref('#ffffff')
|
||||
const liveAnnouncementVisible = ref(false)
|
||||
const currentChallengeInfo = ref<{ type: string; label: string } | null>(null)
|
||||
const pendingChallengeData = ref<{ data: any; receivedAt: number } | null>(null)
|
||||
const pendingSSEEvents = ref<{ type: string; data: any }[]>([])
|
||||
const humanChoices = ref<string[]>([])
|
||||
const humanFightDone = ref(false)
|
||||
const humanFightResult = ref<{ winnerId: string; winnerName: string; isPerfect: boolean } | null>(null)
|
||||
@@ -341,7 +342,20 @@ function connectSSE() {
|
||||
} catch { /* */ }
|
||||
})
|
||||
|
||||
eventSource.onerror = () => { /* SSE reconnects automatically */ }
|
||||
let sseRetries = 0
|
||||
eventSource.onerror = () => {
|
||||
// EventSource auto-reconnects, but if it keeps failing, reconnect manually with backoff
|
||||
sseRetries++
|
||||
if (sseRetries > 5 && eventSource) {
|
||||
eventSource.close()
|
||||
eventSource = null
|
||||
const delay = Math.min(1000 * 2 ** (sseRetries - 5), 10000)
|
||||
setTimeout(() => {
|
||||
if (isLive.value && !eventSource) connectSSE()
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
eventSource.onopen = () => { sseRetries = 0 }
|
||||
}
|
||||
|
||||
function disconnectSSE() {
|
||||
@@ -359,7 +373,10 @@ async function showLiveOverlay(text: string, color: string, duration: number) {
|
||||
|
||||
async function handleRoundEnd(data: any) {
|
||||
const fd = liveFightData.value
|
||||
if (!fd) return
|
||||
if (!fd) {
|
||||
pendingSSEEvents.value.push({ type: 'round_end', data })
|
||||
return
|
||||
}
|
||||
|
||||
const round = data.round
|
||||
const result = data.result
|
||||
@@ -451,7 +468,10 @@ async function handleRoundEnd(data: any) {
|
||||
|
||||
async function handleFightEnd(data: any) {
|
||||
const fd = liveFightData.value
|
||||
if (!fd) return
|
||||
if (!fd) {
|
||||
pendingSSEEvents.value.push({ type: 'fight_end', data })
|
||||
return
|
||||
}
|
||||
|
||||
// Clear challenge
|
||||
humanChallenge.value = null
|
||||
@@ -529,6 +549,12 @@ watch(liveFightData, async (val) => {
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
await initLiveScene()
|
||||
// Drain any SSE events that arrived before liveFightData was ready
|
||||
const queued = pendingSSEEvents.value.splice(0)
|
||||
for (const evt of queued) {
|
||||
if (evt.type === 'round_end') await handleRoundEnd(evt.data)
|
||||
else if (evt.type === 'fight_end') await handleFightEnd(evt.data)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user