fix(kiosk): no reload on first service-worker claim + patient boot health check

Two causes of the fresh-install 'loads, refreshes, loads again' jank:

- The PWA autoUpdate service worker claims the page right after the very
  first load, firing controllerchange, which unconditionally reloaded the
  page. Now only a *replaced* controller (a real update) triggers reload.
- A single 2s RPC echo decided server-down on first boot, flashing the
  BootScreen and then hard-reloading seconds later. A second, 6s attempt
  now runs before concluding the backend is down.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-16 07:05:56 -04:00
parent fa91faa67c
commit 66fd121748
2 changed files with 23 additions and 4 deletions

View File

@ -32,8 +32,18 @@ let updateCallback: (() => Promise<void>) | null = null
onMounted(() => {
// Listen for service worker updates
if ('serviceWorker' in navigator) {
// On the very first visit the page loads with no controlling SW; the
// freshly-installed worker then activates and claims the page
// (autoUpdate clientsClaim), firing controllerchange. Reloading on
// that first claim caused the fresh-install "loads, then reloads"
// jank (worst on kiosk). Only reload when an existing controller is
// REPLACED i.e. a genuine update.
let hadController = !!navigator.serviceWorker.controller
navigator.serviceWorker.addEventListener('controllerchange', () => {
// Service worker updated, reload the page
if (!hadController) {
hadController = true
return
}
window.location.reload()
})

View File

@ -40,10 +40,10 @@ function log(msg: string, data?: unknown) {
sessionStorage.setItem('archipelago_boot_log', prev + entry + '\n')
}
async function quickHealthCheck(): Promise<boolean> {
async function quickHealthCheck(timeoutMs = 2000): Promise<boolean> {
try {
const ac = new AbortController()
const t = setTimeout(() => ac.abort(), 2000)
const t = setTimeout(() => ac.abort(), timeoutMs)
const res = await fetch('/rpc/v1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@ -132,7 +132,16 @@ onMounted(async () => {
return
}
const isUp = await quickHealthCheck()
// First-boot backends can be up but slow (image loads, first-run podman
// work), so a single 2s echo timing out used to flash the BootScreen and
// then hard-reload seconds later ("loads, refreshes, loads again" on
// kiosk). Give it a second, more patient attempt before concluding the
// server is down.
let isUp = await quickHealthCheck()
if (!isUp) {
log('healthCheck retry with longer timeout')
isUp = await quickHealthCheck(6000)
}
log('production flow', { isUp })
if (isUp) {