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
@@ -242,54 +242,61 @@ function shuffle<T>(arr: T[]): T[] {
|
||||
return a
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(r => setTimeout(r, ms))
|
||||
let cycleAbort: AbortController | null = null
|
||||
|
||||
function sleep(ms: number, signal?: AbortSignal) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const id = setTimeout(resolve, ms)
|
||||
signal?.addEventListener('abort', () => { clearTimeout(id); reject(signal.reason) }, { once: true })
|
||||
})
|
||||
}
|
||||
|
||||
let stopCycling = false
|
||||
|
||||
async function cycleTaglines() {
|
||||
async function cycleTaglines(signal: AbortSignal) {
|
||||
const shuffled = shuffle(taglines)
|
||||
let idx = 0
|
||||
|
||||
while (!stopCycling) {
|
||||
const line = shuffled[idx % shuffled.length]
|
||||
isTypingDone.value = false
|
||||
try {
|
||||
while (!signal.aborted) {
|
||||
const line = shuffled[idx % shuffled.length]
|
||||
isTypingDone.value = false
|
||||
|
||||
// Type in
|
||||
for (let i = 0; i <= line.length; i++) {
|
||||
if (stopCycling) return
|
||||
tagline.value = line.slice(0, i)
|
||||
await sleep(35)
|
||||
}
|
||||
isTypingDone.value = true
|
||||
// Type in
|
||||
for (let i = 0; i <= line.length; i++) {
|
||||
if (signal.aborted) return
|
||||
tagline.value = line.slice(0, i)
|
||||
await sleep(35, signal)
|
||||
}
|
||||
isTypingDone.value = true
|
||||
|
||||
// Hold
|
||||
await sleep(4000)
|
||||
if (stopCycling) return
|
||||
// Hold
|
||||
await sleep(4000, signal)
|
||||
|
||||
// Erase
|
||||
isTypingDone.value = false
|
||||
for (let i = line.length; i >= 0; i--) {
|
||||
if (stopCycling) return
|
||||
tagline.value = line.slice(0, i)
|
||||
await sleep(20)
|
||||
}
|
||||
// Erase
|
||||
isTypingDone.value = false
|
||||
for (let i = line.length; i >= 0; i--) {
|
||||
if (signal.aborted) return
|
||||
tagline.value = line.slice(0, i)
|
||||
await sleep(20, signal)
|
||||
}
|
||||
|
||||
await sleep(300)
|
||||
idx++
|
||||
await sleep(300, signal)
|
||||
idx++
|
||||
|
||||
// Reshuffle when we've gone through all
|
||||
if (idx >= shuffled.length) {
|
||||
idx = 0
|
||||
const reshuffled = shuffle(taglines)
|
||||
shuffled.splice(0, shuffled.length, ...reshuffled)
|
||||
// Reshuffle when we've gone through all
|
||||
if (idx >= shuffled.length) {
|
||||
idx = 0
|
||||
const reshuffled = shuffle(taglines)
|
||||
shuffled.splice(0, shuffled.length, ...reshuffled)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// AbortError — expected on unmount
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
cycleTaglines()
|
||||
cycleAbort = new AbortController()
|
||||
cycleTaglines(cycleAbort.signal)
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/fights')
|
||||
@@ -301,7 +308,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopCycling = true
|
||||
if (cycleAbort) { cycleAbort.abort(); cycleAbort = null }
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user