Repository navigation
fix(local-edge): find new workers on release checks and stop cache reads recreating caches - #28
Conversation
…ads recreating caches Worker updates: - An open document never navigates, so browsers checked for a new worker only after the registration was 24 hours stale. Each release check from a controlled document now also calls registration.update() (best-effort). - A reload cannot activate a waiting worker. applyUpdate() now lets a waiting worker take over (bounded to 3 seconds) before reloading, so a user-applied update reaches the new worker as well as the new release. - No change without user action: an open document keeps its worker. Cache Storage: - Release reads used caches.has then caches.open; a delete between the two recreated an empty app-owned cache. Reads and final candidate verification now use caches.match with a cacheName, which never creates. Tests: unit regressions for both (each fails on the previous code), an e2e that switches the worker script mid-session, and a two-tab reset e2e that checks no release cache remains.
…ctivated - Check the waiting worker's state once after attaching the listener, so an update applied in another tab does not wait out the 3 s cap. - Cover the already-activated and other-script branches. - Scope the no-recreation guarantee in architecture.md to kernel reads; the diagnostics panel still opens caches by name.
Independent review (Claude Opus 5.5, read-only
|
|
Correction to the "did not reproduce" note above (2026-10-18). The empty release cache after reset does reproduce on beta.9, in base-app, at about 1 in 6–9 runs. The setup is a beta.5 install, a beta.9 deploy on the same origin, apply update, close and reopen, then reset right away. Instrumentation of the beta.9 worker (open / match / delete logged with timestamps):
So the |
Outcome
Two Local Edge fixes from the FWA backlog. The first is the update lag the owner hit in production, where a worker change needed a manual reset to arrive. The second is a Cache Storage race that can resurrect a deleted release cache.
Behavior change
1. Worker updates arrive without a reset
Handle Fetch), browsers soft-update on every navigation, but on a subresource request only once the registration is more than 86400 s stale. An open SPA document, or a backgrounded iOS WebClip, does not navigate. So a new worker could stay undiscovered for up to a day.registration.update(). That covers startup, visibility, online and the interval.applyUpdate()now posts the existing__fwa:takeovermessage to a waiting worker with the matching script path. It waits until the worker isactivated/redundant(3 s cap), then reloads.skipWaitingwas considered and not adopted.2. Reads never recreate a release cache
readReleaseAsset/isReleaseCompleteusedcaches.hasfollowed bycaches.open. A reset or cleanup deleting the cache between the two calls recreated an empty app-owned cache, which contradicts the offline-shell spec §3.2 (no resurrection).caches.match(path, { cacheName }), which resolvesundefinedand never creates a cache.Non-goals
Risk
applyUpdate()can now swap the controlling worker for other open tabs of the same scope, throughclients.claim()inactivate. This is the same path the incompatible-kernel takeover already uses. Pins live in IndexedDB metadata, so pinned tabs keep their release.loader-contract.md(applyUpdate),config-contract.md(worker discovery) andarchitecture.md.Verification
has→ delete race;applyUpdatereloads.finds a new worker on a release check and applies it with the update. The test server switches the worker script mid-session; a generation probe tells worker 1 from worker 2.Expected "2",Received "1").a reset in another tab leaves no release cache behind. This is invariant coverage. It also passes on the previous code, because the race did not reproduce.pnpm run cipasses: lint, typecheck, 215 unit tests, e2e, hosting, pack check and the packed consumer.main@6954a53). Local Edge, release-update, crash-recovery and navigation-startup e2e ran on Chromium: 48 passed, 1 launch flake that passes in isolation.Limits