fix(02-11): gate three leaked background pollers to activate/deactivate
Demo images / Build & push demo images (push) Has been cancelled

Fleet.vue's useFleetData() (60s telemetry.fleet-status/-alerts poll),
Server.vue's FipsNetworkCard.vue (15s fips.status poll), and Web5.vue's
Web5Monitoring.vue (30s system.stats poll — redundant with Home.vue's
own correctly-gated 10s poll of the same store) all armed their
setInterval in onMounted and only disarmed it in onUnmounted/
onBeforeUnmount. That was harmless before 02-04 registered their
owning views in KEEP_ALIVE_PATHS (the view was destroyed on every
tab-away, so the teardown hook fired every time); once KeepAlive keeps
the instance alive, the teardown hook never fires again and the poll
ran forever in the background regardless of which dashboard tab was
showing.

Gated arm/disarm to onActivated/onDeactivated, mirroring Server.vue's
own vpnPollInterval fix from 02-04 exactly. Added regression tests to
keepAliveLifecycle.test.ts mounting each real component under a
synthetic KeepAlive with fake timers; confirmed RED against the
pre-fix code (git stash) before confirming GREEN with the fix restored.

Full suite (95 files/788 tests), type-check and build all green.
keepAliveTabs.test.ts is byte-for-byte unmodified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-31 09:00:32 -04:00
co-authored by Claude Opus 5
parent e5c38866ca
commit 2c25e512a7
5 changed files with 326 additions and 11 deletions
+32 -5
View File
@@ -1,6 +1,6 @@
/** Composable encapsulating fleet telemetry data fetching, types, and utilities */
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { ref, computed, onMounted, onUnmounted, onActivated, onDeactivated, watch } from 'vue'
import { rpcClient } from '@/api/rpc-client'
import type { ChartDataset } from '@/components/LineChart.vue'
@@ -480,17 +480,44 @@ export function useFleetData() {
// --- Lifecycle ---
// 02-11 gap closure: `startAutoRefresh()`'s 60s poll used to be armed only
// in onMounted and disarmed only in onUnmounted — harmless before Fleet
// joined KEEP_ALIVE_PATHS (02-04), since leaving the tab fully unmounted
// this composable's owning component and onUnmounted fired every time.
// Once Fleet is KeepAlive'd, onUnmounted never fires again after the first
// visit, so the poll ran forever in the background regardless of tab
// visibility — the exact off-screen-drain class 02-04's own audit
// convention exists to prevent, missed here because that audit grepped
// Fleet.vue itself (which has no lifecycle side effects of its own) and
// never extended to this composable it delegates to. Arm/disarm now
// follows activate/deactivate, mirroring Server.vue's vpnPollInterval
// fix from 02-04; `startAutoRefresh()` already clears any existing timer
// first, so calling it from both onMounted and onActivated on a fresh
// KeepAlive-wrapped mount is safe and idempotent.
function armFleetPoll() {
if (autoRefresh.value) startAutoRefresh()
}
function disarmFleetPoll() {
stopAutoRefresh()
}
onMounted(async () => {
updateChartWidth()
window.addEventListener('resize', updateChartWidth)
await refreshAll()
if (autoRefresh.value) {
startAutoRefresh()
}
armFleetPoll()
})
onActivated(() => {
armFleetPoll()
})
onDeactivated(() => {
disarmFleetPoll()
})
onUnmounted(() => {
stopAutoRefresh()
disarmFleetPoll()
window.removeEventListener('resize', updateChartWidth)
})