What it does: Forces all clients below a version threshold to wipe IndexedDB, SW caches, and web storage, then reload with fresh assets.
When to use it: Bad service worker deploy, broken DB schema, corrupted cache — any situation where clients can't self-recover.
How to trigger a reset:
Edit public/clear-manifest.json:
{
"version": "0.0.2",
"clear_below": "0.0.2"
}This forces every client running a version below 0.0.2 to clear everything and reload. Set clear_below to null to disable.
How to revert: Set clear_below back to null and deploy. Clients that already cleared are unaffected (they're on the new version). Clients that haven't loaded yet won't clear.
What gets cleared:
| Target | What's wiped |
|---|---|
| IndexedDB | All databases (message cache, search index, drafts) |
| SW caches | All Workbox precache + runtime caches |
| Service worker | Unregistered (fresh SW installs on reload) |
| localStorage | Cleared (settings, preferences) |
| sessionStorage | Cleared (tab-scoped state) |
| Cookies | Not touched (auth sessions preserved) |
CI safety net: On every PR, CI checks if changed files match patterns in .clear-site-data.json. If they do, it posts a warning comment reminding you to consider updating the manifest.
Files involved:
| File | Purpose |
|---|---|
public/clear-manifest.json |
The manifest — edit this to trigger/disable clears |
src/main.ts → checkClearManifest() |
Boot check — runs before any DB/store init |
.clear-site-data.json |
Maps file patterns to clear targets (used by CI) |
scripts/check-clear-manifest.js |
CI script that warns on PRs |
workbox.config.cjs |
Excludes manifest from SW precache |
checkClearManifest() in src/main.ts runs at the top of bootstrap(), before any database or store initialization:
- Fetches
/clear-manifest.jsonwithcache: 'no-store'(bypasses HTTP cache) - Reads
clear_belowfrom the manifest - Compares the client's
VITE_PKG_VERSION(semver, e.g.0.0.1) against the threshold - If the client is below the threshold:
- Deletes all IndexedDB databases (with Safari fallback for
indexedDB.databases()) - Deletes all SW caches via
caches.keys()+caches.delete() - Unregisters the service worker
- Clears localStorage and sessionStorage
- Calls
window.location.reload()
- Deletes all IndexedDB databases (with Safari fallback for
- If the fetch fails or
clear_belowisnull, continues normally — the app boots as usual
After clearing and reloading, the browser fetches fresh assets from the CDN (Cloudflare cache is purged on every deploy). The fresh JS bundle has the current VITE_PKG_VERSION baked in at build time. Since the current version is >= clear_below, the check passes through. No cleared_at tracking needed.
The manifest is excluded from the service worker's control:
globIgnoresinworkbox.config.cjsprevents precachingclear-manifest.jsonnavigateFallbackDenylistincludes/clear-manifest\.json$/so the SW won't serveindex.htmlfor itcache: 'no-store'on the fetch request bypasses the HTTP cache
This means even a broken SW won't intercept the manifest fetch. Combined with updateViaCache: 'none' on SW registration, the browser always checks for a fresh SW file and the manifest is always fetched from the network.
The existing CI pipeline already purges the entire Cloudflare cache on every deploy to main. This ensures the new clear-manifest.json is served immediately after deploy — no stale copies at the edge.
.clear-site-data.json in the repo root maps file patterns to clear targets:
{
"triggers": {
"cache": ["public/sw-sync.js", "workbox.config.cjs", "src/workers/sync.worker.ts"],
"storage": [
"src/utils/db-constants.ts",
"src/utils/db.js",
"src/utils/db-recovery.js",
"src/workers/db.worker.ts"
]
}
}cachetriggers — files that affect SW behavior or cache strategystoragetriggers — files that affect IndexedDB schema or database recovery
On every pull request, CI runs scripts/check-clear-manifest.js which:
- Diffs changed files between the PR branch and the base branch
- Matches against glob patterns in
.clear-site-data.json - If any match, posts a PR comment:
## Clear-Site-Data Warning
The following changes may require updating `public/clear-manifest.json`.
If this release changes DB schema, service worker behavior, or cache strategy,
set `clear_below` to the current version to force client resets.
### Target: `storage`
- `src/utils/db-constants.ts`
The check uses continue-on-error: true — it warns but never blocks the build. Existing comments are updated rather than duplicated.
When automation underfires (refactors, renames, generated files that don't match patterns), manually edit clear-manifest.json in the same PR. The CI warning is advisory, not the only path to a reset.
- Decide the version threshold — typically the version you're about to release
- Edit
public/clear-manifest.json:{ "_docs": "Kill switch for bad releases. Set clear_below to a semver version to force all older clients to wipe local data and reload. Set to null to disable. See docs/clear-site-data-spec.md", "version": "0.0.2", "clear_below": "0.0.2" } - Commit and deploy to main
- All clients below
0.0.2will clear on their next page load
If a bad release is already live and you need to force resets for clients on that version:
- Bump the version in
package.json - Set
clear_belowto the new version - Push to main — CI builds, deploys, and purges CDN cache
- Clients on the bad version clear and reload with the fix
Set clear_below to null and deploy:
{
"version": "0.0.2",
"clear_below": null
}After deploying a reset, check browser dev tools console for:
[clear-manifest] Client version 0.0.1 is below 0.0.2 — clearing site data
If you don't see this, check:
- Is the manifest being served fresh? (
curl -I https://your-domain/clear-manifest.json) - Is the SW intercepting it? (check Network tab — should show
(disk cache)or network, not SW) - Is
VITE_PKG_VERSIONcorrect in the build? (checkimport.meta.env.VITE_PKG_VERSIONin console)
T1. False positive — accidental mass data wipe
Innocent refactor touches a trigger file, someone bumps clear_below without understanding impact. Impact: users lose offline data, drafts, cached settings. Mitigations: CI posts PR warnings when trigger files change; clear_below change is visible in PR diff; data re-syncs from server after clear. Residual risk: medium (human error), but auditable and reversible.
T2. False negative — missed clear, unrecoverable client
DB schema change in an unmapped file. No CI warning, no one updates the manifest, clients brick. Impact: silent breakage, support tickets. Mitigations: directory-based globs in .clear-site-data.json, periodic audit of IndexedDB usage vs. impact map, manual override always available. Residual risk: medium — but this is the status quo today, and this system strictly improves on it.
T3. Infinite reload loop
Client clears, reloads, clears again. Impact: app unusable. Why it can't happen: after clearing, the reload fetches fresh assets with the current VITE_PKG_VERSION baked in. Current version >= clear_below, check passes. The only scenario is if the CDN serves stale JS after the manifest — but the CI pipeline purges the entire Cloudflare cache atomically.
T4. Malicious manifest injection
Attacker compromises R2 bucket, deploys { "clear_below": "999.0.0" }. All users lose local data. Impact: data loss (but recoverable from server). Mitigations: R2 write access restricted to CI via scoped API credentials; manifest is a static JSON file, not executable; auth cookies preserved so users stay logged in. Residual risk: low if infra credentials are secured.
T5. Broken SW blocks manifest fetch
Old SW intercepts /clear-manifest.json and serves stale copy. Impact: recovery never triggers. Mitigations: manifest excluded from SW precache via globIgnores; excluded from navigateFallback via denylist; fetched with cache: 'no-store'; SW registered with updateViaCache: 'none' so browser always checks for fresh SW. Residual risk: very low.
T6. Race during deploy
User has tab open, CDN serves new manifest before new JS assets. Boot check runs with old VITE_PKG_VERSION, triggers clear. Impact: user loses local data on next page load — but this is the intended behavior. The clear is correct; the client needs the new version. Active sessions are unaffected (check only runs on page load, not mid-session).
| Category | Risk |
|---|---|
| Data loss (false positive) | Medium — auditable and reversible |
| Availability (reload loops) | Very low |
| Security compromise | Low |
| Operational error | Medium — mitigated by CI warnings |
| Recovery failure (false negative) | Much lower than status quo |
These are not implemented. They're documented here for when/if the simple approach proves insufficient.
Granular targets — instead of clearing everything, clear only cache or only storage based on what changed. Would require a targets field in the manifest and conditional clearing in checkClearManifest().
Version ranges — instead of a single clear_below, support multiple ranges for cumulative clears across releases. Would require a ranges array and semver comparison logic.
Cloudflare Worker (Option B) — if a broken SW ever prevents the client-side check from running, a thin Cloudflare Worker could read a version cookie and inject a Clear-Site-Data HTTP header at the edge, bypassing all client-side code.
Manifest signing — embed a SHA256 hash of the manifest in the build to detect tampering. The boot check would verify the hash before acting on the manifest.
Automated manifest updates — have CI automatically set clear_below when trigger files change, removing the manual step. Deferred because the manual step is a safety feature, not a burden, at current release velocity.