refactor: extract shared FightData interface to fight/types.ts

Move FightData, FightRound, FightBot, FightArenaInfo types from local
definitions in FightViewer.vue to shared fight/types.ts. Replace `any`
typing in HumanFightPage, FightPage, useFightPolling, and useFightCache
with proper typed interfaces. Fix null-safety guards exposed by typing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 06:19:26 +00:00
co-authored by Claude Opus 4.6
parent 2f7c10e4b8
commit 945a776a5a
6 changed files with 81 additions and 48 deletions
+10 -10
View File
@@ -139,8 +139,8 @@ async function initLiveScene() {
try {
liveScene = await createFightScene({
canvas: liveCanvas.value,
botA: { name: data.botA.name, seed: data.botA.avatarSeed || data.botA.name, tier: data.botA.tier, archetype: data.botA.archetype, customization: data.botA.customization, wins: data.botA.wins, losses: data.botA.losses },
botB: { name: data.botB.name, seed: data.botB.avatarSeed || data.botB.name, tier: data.botB.tier, archetype: data.botB.archetype, customization: data.botB.customization, wins: data.botB.wins, losses: data.botB.losses },
botA: { name: data.botA.name, seed: data.botA.avatarSeed || data.botA.name, tier: data.botA.tier, archetype: data.botA.archetype, customization: data.botA.customization ?? undefined, wins: data.botA.wins, losses: data.botA.losses },
botB: { name: data.botB.name, seed: data.botB.avatarSeed || data.botB.name, tier: data.botB.tier, archetype: data.botB.archetype, customization: data.botB.customization ?? undefined, wins: data.botB.wins, losses: data.botB.losses },
arena: data.arena,
})
} catch (err) {
@@ -219,7 +219,7 @@ async function showLiveOverlay(text: string, color: string, duration: number) {
async function handleRoundEnd(data: any) {
const fd = liveFightData.value
if (!fd) {
if (!fd || !fd.botA || !fd.botB) {
pendingSSEEvents.value.push({ type: 'round_end', data })
return
}
@@ -297,7 +297,7 @@ async function handleRoundEnd(data: any) {
async function handleFightEnd(data: any) {
const fd = liveFightData.value
if (!fd) {
if (!fd || !fd.botA || !fd.botB) {
pendingSSEEvents.value.push({ type: 'fight_end', data })
return
}
@@ -746,7 +746,7 @@ function stopAutoBattle() {
<div class="h-full bg-neon-pink transition-all duration-500 ml-auto" :style="{ width: `${liveHpB}%` }" />
</div>
<span class="font-mono font-bold text-[10px] w-5 text-left tabular-nums" :class="liveHpB > 50 ? 'text-neon-pink' : liveHpB > 20 ? 'text-neon-yellow' : 'text-ko'">{{ liveHpB }}</span>
<p class="font-marker text-[10px] tracking-wider truncate text-right text-neon-pink flex-1 min-w-0">{{ liveFightData.botB.name }}</p>
<p class="font-marker text-[10px] tracking-wider truncate text-right text-neon-pink flex-1 min-w-0">{{ liveFightData.botB?.name }}</p>
</div>
</div>
<!-- Desktop: single-row layout -->
@@ -762,7 +762,7 @@ function stopAutoBattle() {
<div class="flex-1 h-5 bg-black/50 rounded-sm overflow-hidden border border-neon-pink/30">
<div class="h-full bg-gradient-to-l from-neon-pink to-neon-purple transition-all duration-500 ml-auto" :style="{ width: `${liveHpB}%` }" />
</div>
<p class="font-marker text-sm tracking-wider truncate text-right text-neon-pink flex-shrink-0 max-w-[20%]">{{ liveFightData.botB.name }}</p>
<p class="font-marker text-sm tracking-wider truncate text-right text-neon-pink flex-shrink-0 max-w-[20%]">{{ liveFightData.botB?.name }}</p>
</div>
<div class="flex items-center justify-between mt-0.5">
<span class="font-pixel text-[9px]" :class="tierClass(liveFightData.botA.tier || 0)">{{ Math.round(liveFightData.botA.eloRating || 0) }}</span>
@@ -771,7 +771,7 @@ function stopAutoBattle() {
<span v-if="liveFightData.mode === 'ranked'" class="text-neon-cyan"> | {{ liveFightData.potSats || 42 }} SATS</span>
<span v-if="spectatorCount > 0" class="text-neon-cyan"> | {{ spectatorCount }} watching</span>
</span>
<span class="font-pixel text-[9px]" :class="tierClass(liveFightData.botB.tier || 0)">{{ Math.round(liveFightData.botB.eloRating || 0) }}</span>
<span class="font-pixel text-[9px]" :class="tierClass(liveFightData.botB?.tier || 0)">{{ Math.round(liveFightData.botB?.eloRating || 0) }}</span>
</div>
</div>
</div>
@@ -900,7 +900,7 @@ function stopAutoBattle() {
<div class="h-full bg-neon-pink transition-all duration-500 ml-auto" :style="{ width: `${liveHpB}%` }" />
</div>
<span class="font-mono font-bold text-[10px] w-5 text-left tabular-nums" :class="liveHpB > 50 ? 'text-neon-pink' : liveHpB > 20 ? 'text-neon-yellow' : 'text-ko'">{{ liveHpB }}</span>
<p class="font-marker text-[10px] tracking-wider truncate text-right text-neon-pink flex-1 min-w-0">{{ liveFightData.botB.name }}</p>
<p class="font-marker text-[10px] tracking-wider truncate text-right text-neon-pink flex-1 min-w-0">{{ liveFightData.botB?.name }}</p>
</div>
</div>
<!-- Desktop: single-row layout -->
@@ -916,7 +916,7 @@ function stopAutoBattle() {
<div class="flex-1 h-5 bg-black/50 rounded-sm overflow-hidden border border-neon-pink/30">
<div class="h-full bg-gradient-to-l from-neon-pink to-neon-purple transition-all duration-500 ml-auto" :style="{ width: `${liveHpB}%` }" />
</div>
<p class="font-marker text-sm tracking-wider truncate text-right text-neon-pink flex-shrink-0 max-w-[20%]">{{ liveFightData.botB.name }}</p>
<p class="font-marker text-sm tracking-wider truncate text-right text-neon-pink flex-shrink-0 max-w-[20%]">{{ liveFightData.botB?.name }}</p>
</div>
<div class="flex items-center justify-between mt-0.5">
<span class="font-pixel text-[9px]" :class="tierClass(liveFightData.botA.tier || 0)">{{ Math.round(liveFightData.botA.eloRating || 0) }}</span>
@@ -924,7 +924,7 @@ function stopAutoBattle() {
{{ liveFightData.arenaInfo?.name }} | R{{ liveCurrentRound }}
<span v-if="spectatorCount > 0" class="text-neon-cyan ml-1">| {{ spectatorCount }} watching</span>
</span>
<span class="font-pixel text-[9px]" :class="tierClass(liveFightData.botB.tier || 0)">{{ Math.round(liveFightData.botB.eloRating || 0) }}</span>
<span class="font-pixel text-[9px]" :class="tierClass(liveFightData.botB?.tier || 0)">{{ Math.round(liveFightData.botB?.eloRating || 0) }}</span>
</div>
</div>
</div>