The visible problem may look like one API or tuning value, but reliability is decided by state ownership, resource bounds, and recovery after failure. A worker version can coexist with old pages. Deleting every old cache on activate can break dynamic imports or images in those pages.
Offline UX distinguishes locally available, pending sync, and server-confirmed states. Cache versions, command queues, and conflict resolution belong together or reconnecting amplifies collisions between old actions and new facts.
Make the implementation decisions explicit
Turn the important choices into durable contracts: validate inputs, assign state ownership, define cleanup, and specify fallback for older peers. Later optimization must not change those semantics.
- Name static caches by build hash, activate only after complete install, retain current plus previous or inspect client references, and bound article caches with LRU.
- Bound every input by size, count, and time, returning a stable actionable error code when a budget is exceeded.
- Retries need an idempotency key, backoff, and deadline; after the deadline create a new task instead of reviving old callbacks.
The delivery standard for Cache Versioning and Cleanup Without Deleting Live Assets is a usable normal path, convergent failures, bounded resources, and a state users can understand. The result is a production capability that can be explained, degraded safely, and rolled back—not a demo that works once.
Failure paths that are easy to miss
Boundaries turn hidden assumptions into incidents. Weak networks, refresh, concurrency, and capacity need combined coverage because retries can hide each one in isolation.
- Deleting old cache after partial install breaks offline startup, while never evicting articles makes storage grow indefinitely after two hundred posts.
- Refresh and network change start two recovery paths, and duplicate side effects look like two genuine user actions.
- Without backpressure or quota, a slow consumer raises memory, queue depth, and tail latency until unrelated users are affected.
How to verify it before release
Write the expected state trace before injecting faults. At every phase, reconcile user-visible outcome, both protocol endpoints, persistent records, and resource counts to prove the loop.
- Interrupt precache, keep an old tab, release two versions, and browse all articles; verify usable versions, bytes, LRU, rollback, and no orphans.
- Drive the state machine with reordered, duplicate, and delayed messages, proving stale versions are ignored and explicit stop survives recovery.
- Allowlist log and analytics fields, proving payloads, secrets, full IP addresses, and identifying data never leave the device.
Completion is not one passing path. Every terminal state reconciles, automation stays below user intent, and every operational cost has an explicit ceiling.