fix(sys-bar+fix-sw): accurate button labels and full storage reset

- sys bar: fix wrong title on Full Reset button (was "Clear IDB only",
  now accurately describes SW + Cache + IDB + localStorage + sessionStorage)
- sys bar: rename "Reload Page" → "Clear IDB & Reload" and wire it to
  handle_clear_idb_only instead of a bare window.location.reload()
- testing/fix-sw: expand from SW+Cache only to full storage nuke —
  adds IDB (via indexedDB.databases() with known-names fallback),
  localStorage, sessionStorage; auto-reloads to / after 3s; shows
  per-step status log with success/warn/error colour coding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-24 12:44:02 -04:00
parent dda7a91f9f
commit a10b4d18bb
2 changed files with 159 additions and 50 deletions

View File

@@ -549,24 +549,24 @@ function apply_theme(value: string) {
</a>
{/if}
<!-- Reload -->
<!-- Clear IDB + Reload (preserves localStorage/sessionStorage + SW) -->
<button
type="button"
class="btn btn-sm preset-tonal-warning w-full justify-end"
onclick={() => window.location.reload()}
title="Hard reload the page">
onclick={handle_clear_idb_only}
title="Clear IDB & Reload: delete all IndexedDB databases, then reload. Preserves localStorage, sessionStorage, and service worker cache.">
<RefreshCw size="1em" class="opacity-60" />
Reload Page
Clear IDB & Reload
</button>
<!-- Clear localStorage, sessionStorage, and all IndexedDB databases — full reset -->
<!-- Full reset: SW + Cache Storage + IDB + localStorage + sessionStorage -->
<button
type="button"
class="btn btn-sm preset-tonal-error w-full justify-end"
onclick={handle_clear_storage_and_idb}
title="Clear all IndexedDB tables only then reload">
title="Full Reset: unregister service workers, clear SW Cache Storage, delete all IndexedDB databases, clear localStorage and sessionStorage, then reload.">
<Eraser size="1em" class="opacity-60" />
Clear All Storage + IDB
Full Reset
</button>
<!-- URL param builder -->

View File

@@ -2,75 +2,184 @@
import { onMount } from 'svelte';
let status: string[] = $state([]);
let done = $state(false);
let has_error = $state(false);
function log(msg: string) {
status = [...status, `${new Date().toLocaleTimeString()} - ${msg}`];
console.log(`[FIX-SW] ${msg}`);
}
async function nukeServiceWorkers() {
log('Starting Service Worker cleanup...');
function log_ok(msg: string) {
log(`✓ ${msg}`);
}
if (!('serviceWorker' in navigator)) {
log('Service Workers not supported in this browser.');
return;
}
function log_warn(msg: string) {
log(`⚠ ${msg}`);
}
async function nuke_everything() {
log('Starting full storage reset...');
// 1. Unregister all service workers
if ('serviceWorker' in navigator) {
try {
const registrations = await navigator.serviceWorker.getRegistrations();
log(`Found ${registrations.length} registrations.`);
for (const registration of registrations) {
const scope = registration.scope;
log(`Unregistering SW at scope: ${scope}`);
const success = await registration.unregister();
log(`Unregister success: ${success}`);
}
if (registrations.length === 0) {
log('No active Service Workers found.');
log_warn('No active service worker registrations found.');
} else {
log(`Found ${registrations.length} service worker registration(s).`);
for (const reg of registrations) {
const ok = await reg.unregister();
log_ok(`Unregistered SW at scope: ${reg.scope} (success: ${ok})`);
}
log('Clearing Cache Storage...');
const cacheKeys = await caches.keys();
for (const key of cacheKeys) {
log(`Deleting cache: ${key}`);
await caches.delete(key);
}
log('Cache storage cleared.');
log('DONE. Please reload the application now.');
} catch (err: any) {
log(`ERROR: ${err.message}`);
console.error(err);
has_error = true;
log(`ERROR unregistering service workers: ${err.message}`);
}
} else {
log_warn('Service Workers not supported in this browser.');
}
// 2. Clear all Cache Storage caches
try {
const cache_keys = await caches.keys();
if (cache_keys.length === 0) {
log_warn('No Cache Storage entries found.');
} else {
for (const key of cache_keys) {
await caches.delete(key);
log_ok(`Cleared cache: ${key}`);
}
}
} catch (err: any) {
has_error = true;
log(`ERROR clearing Cache Storage: ${err.message}`);
}
// 3. Clear all IndexedDB databases
try {
if ('databases' in indexedDB) {
const db_list = await indexedDB.databases();
if (db_list.length === 0) {
log_warn('No IndexedDB databases found.');
} else {
for (const db_info of db_list) {
if (!db_info.name) continue;
await new Promise<void>((resolve, reject) => {
const req = indexedDB.deleteDatabase(db_info.name!);
req.onsuccess = () => {
log_ok(`Deleted IDB database: ${db_info.name}`);
resolve();
};
req.onerror = () => {
log(`ERROR deleting IDB database: ${db_info.name}`);
has_error = true;
resolve(); // continue anyway
};
req.onblocked = () => {
log_warn(`IDB delete blocked (open connections): ${db_info.name} — will proceed`);
resolve();
};
});
}
}
} else {
// Fallback: delete known Aether databases by name
log_warn('indexedDB.databases() not available — deleting known Aether databases by name.');
const known_dbs = [
'ae_core_db', 'ae_events_db', 'ae_journals_db',
'ae_archives_db', 'ae_posts_db', 'ae_idaa_db',
'ae_sponsorships_db', 'ae_reports_db',
];
for (const name of known_dbs) {
await new Promise<void>((resolve) => {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = () => { log_ok(`Deleted IDB database: ${name}`); resolve(); };
req.onerror = () => { resolve(); }; // not present = silent
req.onblocked = () => { log_warn(`IDB delete blocked: ${name}`); resolve(); };
});
}
}
} catch (err: any) {
has_error = true;
log(`ERROR clearing IndexedDB: ${err.message}`);
}
// 4. Clear localStorage
try {
const local_count = localStorage.length;
localStorage.clear();
log_ok(`Cleared localStorage (${local_count} item(s)).`);
} catch (err: any) {
has_error = true;
log(`ERROR clearing localStorage: ${err.message}`);
}
// 5. Clear sessionStorage
try {
const session_count = sessionStorage.length;
sessionStorage.clear();
log_ok(`Cleared sessionStorage (${session_count} item(s)).`);
} catch (err: any) {
has_error = true;
log(`ERROR clearing sessionStorage: ${err.message}`);
}
log('─── Reset complete. Reloading in 3 seconds... ───');
done = true;
// Auto-reload after a short pause so the user can read the log
await new Promise((r) => setTimeout(r, 3000));
window.location.href = '/';
}
onMount(() => {
nukeServiceWorkers();
nuke_everything();
});
</script>
<div class="mx-auto max-w-2xl p-8 font-mono">
<h1 class="mb-4 text-2xl font-bold text-red-600">
Service Worker Reset Tool
</h1>
<p class="mb-4">
Attempting to unregister all service workers and clear caches to fix the
TypeError loop.
<h1 class="mb-2 text-2xl font-bold text-red-600">Full Storage Reset</h1>
<p class="mb-1 text-sm text-gray-600">
Clears <strong>everything</strong> for this domain: service workers, SW cache, IndexedDB,
localStorage, sessionStorage — then reloads.
</p>
<p class="mb-4 text-sm text-gray-500">
Use this if you are completely stuck on an old version of the app and nothing else has
worked.
</p>
<div class="min-h-[200px] rounded border border-gray-300 bg-gray-100 p-4">
<div
class="min-h-60 rounded border border-gray-300 bg-gray-950 p-4 text-green-400 text-sm">
{#each status as line, i (i)}
<div class="border-b border-gray-200 py-1 last:border-0">
<div
class="py-0.5 {line.includes('ERROR')
? 'text-red-400'
: line.includes('⚠')
? 'text-yellow-400'
: ''}">
{line}
</div>
{/each}
{#if !done}
<div class="mt-2 animate-pulse text-gray-500">Working...</div>
{/if}
</div>
{#if done}
<div class="mt-4 flex items-center gap-4">
<p class="text-sm {has_error ? 'text-yellow-600' : 'text-green-700'} font-semibold">
{has_error
? 'Reset finished with some errors. Reloading...'
: 'Reset complete! Reloading to home page...'}
</p>
<button
class="mt-4 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
onclick={() => window.location.reload()}>
Reload Page
class="ml-auto rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
onclick={() => (window.location.href = '/')}>
Reload Now
</button>
</div>
{/if}
</div>