Build a field case-review app for inspector 47, who records evidence while the connection is absent. A draft has a stable local ID, account scope, case ID, server base revision, edit time, and synchronization state. A worker may cache the application shell and public static assets, but it never treats a private case response as shared shell content. The page explains whether each edit exists only on this device, has reached the server, or needs a conflict decision. A queued item is acknowledged only after the server accepts its idempotency key and returns the committed revision. A tab opened on release 24 can finish its edit even when release 25 has installed. The waiting worker asks for a safe activation point; it does not replace a page halfway through an unsent save. The database upgrade uses a versionchange transaction, closes old connections when asked, and explains blocked upgrades to other tabs. Before clearing old records, the app checks that each item has server confirmation and that any promised export is complete. Quota failures stop new offline capture with a visible action path. They do not turn into a false saved indicator. When inspector 47 signs out and inspector 62 signs in, local drafts, caches, and any pending sync handle are scoped and cleared according to policy before the second account can see them.
Project: offline case review across upgrades
Build contract
- Keep pending drafts readable through a worker activation and database upgrade.
- Show a blocked-tab recovery path without discarding its unsent data.
- Handle quota failure and eviction as ordinary states in the interface.
- Prove one account cannot read another account's offline case content.
Implementation checkpoint
function mayActivateRelease(clients) {
return clients.every(client => client.pendingWrites === 0 && client.schemaReady);
}
console.log(mayActivateRelease([{ pendingWrites: 0, schemaReady: true }, { pendingWrites: 2, schemaReady: true }]));
// Output: falseCost and boundaries
An IndexedDB index on account and sync state makes a pending-work query depend on matching records rather than a scan of every case. The worker activation check is O(number of controlled clients), while checking pending records per client depends on its store index. Holding both releases' static assets costs temporary storage. Keep only supported versions and clean old assets after a confirmed transition. Each queued draft consumes bytes until acknowledged; measure local count and estimated usage, but never infer that a browser quota estimate is an exact future guarantee. Exports and server copies cost extra storage and network traffic, which is the price of a genuine recovery path.
Failure drill
Start an unsent edit in release 24 and install release 25 in another tab. Leave the first tab open, trigger a blocked database upgrade, and confirm the old tab remains readable with a prompt to finish or export its work. Close it and verify the new schema opens before the new worker takes control. Fill storage until an insert fails; the page must report that the new photo is not saved and preserve the prior drafts. Clear site data from browser settings and reopen the app. It should report missing local work honestly and load the last committed server copy. Switch accounts while a sync request is delayed, then deliver the old response and confirm it cannot populate the new account's interface.
Acceptance checks
- A pending edit is never described as server saved.
- The old tab closes or exports before a blocked upgrade can complete.
- Quota and eviction paths preserve an honest state report.
- Account switches invalidate stale sync responses and private caches.
Common Mistakes
- Calling skipWaiting while unsent work belongs to an open client.
- Deleting old stores before verifying migration and sync state.
- Treating a quota estimate as a reservation for future writes.
Related lessons
Service Worker Activation and Unsent Work; IndexedDB Version Changes and Blocked Tabs; Browser Storage Quota, Eviction, and Recovery; Private Offline Cache and Account Switch.
