From 119cd2cf83f4fb7f3b903e01b785d548eee078b8 Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 27 Apr 2026 01:01:35 +0200 Subject: [PATCH] chore(boot): sweep orphan migration flags from localStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two one-shot bootstraps left a per-user flag in localStorage so they wouldn't run twice — and after F7 deleted the helpers themselves (2a8e8ff98), the flags pointed at code that no longer existed: mana.profile.silentTwinRepair. mana.profile.avatarMigration. New \`cleanupOrphanMigrationFlags()\` runs once per page load from the (app) layout's onMount, right after \`restoreClientIdFromDexie()\`. Cheap (single localStorage scan), idempotent (no-op once swept), silent on private-mode / quota errors. The known-orphan prefix list lives in the helper file with deletion-commit refs so it's clear when each entry can be retired. Future migration deletions: append the prefix to ORPHAN_KEY_PREFIXES in the same commit that drops the helper, and the next page load on every device cleans up. Closes Punkt 8 of the F1-F7 follow-up audit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../web/src/lib/data/migrations-cleanup.ts | 66 +++++++++++++++++++ .../apps/web/src/routes/(app)/+layout.svelte | 4 ++ 2 files changed, 70 insertions(+) create mode 100644 apps/mana/apps/web/src/lib/data/migrations-cleanup.ts diff --git a/apps/mana/apps/web/src/lib/data/migrations-cleanup.ts b/apps/mana/apps/web/src/lib/data/migrations-cleanup.ts new file mode 100644 index 000000000..cdc6dd442 --- /dev/null +++ b/apps/mana/apps/web/src/lib/data/migrations-cleanup.ts @@ -0,0 +1,66 @@ +/** + * One-shot cleanup of orphan localStorage keys left behind by deleted + * migration helpers. + * + * When a one-shot bootstrap (e.g. `repairSilentTwinAvatarRows`) finishes + * the first time, it sets a per-user `mana.profile..` flag in + * localStorage so it doesn't run again. After the helper itself is + * deleted (F7 of docs/plans/sync-field-meta-overhaul.md), the flag stays + * behind forever — a small chunk of dead state that piles up if more + * helpers come and go. + * + * This module sweeps the known orphan prefixes and removes any matching + * key on every page load. Cheap (a single localStorage scan, capped by + * the browser's quota) and idempotent — calling it twice is a no-op + * the second time because the keys are already gone. + * + * Add a new prefix here whenever a migration helper is deleted in a + * future commit, then remove the prefix again once enough time has + * passed that no live device still carries the orphan flag. + */ + +/** + * Known dead localStorage prefixes. Keep in chronological order with a + * comment naming the deletion commit so it's clear when the entry can + * be removed once enough time has passed. + */ +const ORPHAN_KEY_PREFIXES: readonly string[] = [ + // F7: `repair-silent-twin.ts` deleted in 2a8e8ff98 (sync field-meta + // overhaul). The helper guarded itself with this flag so it'd only + // run once per user; the flag now points at code that no longer + // exists. + 'mana.profile.silentTwinRepair.', + // F7: `legacy-avatar.ts` deleted in the same commit, same flag + // pattern, same orphan situation. + 'mana.profile.avatarMigration.', +]; + +/** + * Remove every localStorage key matching one of {@link ORPHAN_KEY_PREFIXES}. + * + * Best-effort: failures (private mode, quota errors, locked storage on + * some browsers) are swallowed silently — orphan flags are cosmetic, not + * load-bearing. + */ +export function cleanupOrphanMigrationFlags(): void { + if (typeof localStorage === 'undefined') return; + const toRemove: string[] = []; + try { + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (!key) continue; + if (ORPHAN_KEY_PREFIXES.some((prefix) => key.startsWith(prefix))) { + toRemove.push(key); + } + } + } catch { + return; + } + for (const key of toRemove) { + try { + localStorage.removeItem(key); + } catch { + // Storage locked / private mode — next reload retries. + } + } +} diff --git a/apps/mana/apps/web/src/routes/(app)/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/+layout.svelte index c607674cd..961b0013d 100644 --- a/apps/mana/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+layout.svelte @@ -70,6 +70,7 @@ stopMemoroLlmWatcher, } from '$lib/modules/memoro/llm-watcher.svelte'; import { createUnifiedSync, restoreClientIdFromDexie } from '$lib/data/sync'; + import { cleanupOrphanMigrationFlags } from '$lib/data/migrations-cleanup'; import { syncBilling } from '$lib/stores/sync-billing.svelte'; import { networkStore } from '$lib/stores/network.svelte'; import { db } from '$lib/data/database'; @@ -632,6 +633,9 @@ // so the next push/pull keeps the same identity the server // already knows. await restoreClientIdFromDexie(); + // Sweep stale localStorage flags from migration helpers that + // have since been deleted (F7 + future cleanups). + cleanupOrphanMigrationFlags(); const getToken = () => authStore.getValidToken(); unifiedSync = createUnifiedSync(SYNC_SERVER_URL, getToken, syncBilling.active); // Expose on window for SYNC_DEBUG.md (Schritt C). Not a security