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
+31 -3
View File
@@ -119,7 +119,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, onActivated, onDeactivated, ref } from 'vue'
import { rpcClient } from '@/api/rpc-client'
import { useCachedResource } from '@/composables/useCachedResource'
import { safeClipboardWrite } from '@/views/web5/utils'
@@ -262,14 +262,42 @@ async function reconnectAnchor() {
// Poll instead of one-shot: a long-lived page (the kiosk especially) was
// stuck showing whatever anchor state existed at mount time forever.
//
// 02-11 gap closure: this card is rendered inside Server.vue, which joined
// KEEP_ALIVE_PATHS in 02-04 — but this poll was only ever armed in
// onMounted and disarmed in onUnmounted, so once Server.vue (and this card
// with it) is KeepAlive'd, onUnmounted never fires again and the poll ran
// forever in the background, firing `fips.status` every 15s regardless of
// which dashboard tab was actually showing (`document.hidden` only helps
// for a backgrounded BROWSER tab, not an in-SPA tab switch). Arm/disarm now
// follows activate/deactivate, mirroring Server.vue's own vpnPollInterval
// fix from 02-04; calling armFipsPoll from both onMounted and onActivated
// on a fresh KeepAlive-wrapped mount is safe since it always clears any
// prior timer first.
let statusInterval: ReturnType<typeof setInterval> | null = null
onMounted(() => {
function armFipsPoll() {
if (statusInterval) clearInterval(statusInterval)
statusInterval = setInterval(() => {
if (document.hidden) return
void statusRes.refresh()
}, 15000)
}
function disarmFipsPoll() {
if (statusInterval) {
clearInterval(statusInterval)
statusInterval = null
}
}
onMounted(() => {
armFipsPoll()
})
onActivated(() => {
armFipsPoll()
})
onDeactivated(() => {
disarmFipsPoll()
})
onUnmounted(() => {
if (statusInterval) clearInterval(statusInterval)
disarmFipsPoll()
})
</script>