feat: add store_versions.ts — localStorage schema versioning for ae_loc and ae_events_loc

Persistent stores grow and change over time. svelte-persisted-store deep-merges
old localStorage values with new defaults, so stale values (e.g. hash_prefix_length: 1)
silently survive schema changes and cause subtle bugs.

- src/lib/stores/store_versions.ts:
  Single source of truth for AE_LOC_VERSION / AE_EVENTS_LOC_VERSION.
  Side-effect on import: reads raw localStorage and wipes if __version mismatches.
  Must be imported first in ae_stores.ts and ae_events_stores.ts so the wipe
  happens before persisted() hydrates from localStorage.

- ae_stores.ts + ae_events_stores.ts:
  Import store_versions as first import; add __version to persisted store defaults.

- documentation/TODO__Agents.md:
  Added stores refactor task — both store files need a cleanup pass.

Bump AE_LOC_VERSION or AE_EVENTS_LOC_VERSION by 1 on breaking schema changes.
Non-breaking changes (new optional fields, default value tweaks) do not need a bump.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-11 12:43:05 -04:00
parent ca91afbd9d
commit 241e05bc79
4 changed files with 79 additions and 0 deletions

View File

@@ -7,6 +7,15 @@
## 🚧 Upcoming High Priority
### [Stores] Refactor — ae_stores.ts and ae_events_stores.ts cleanup
Both files have grown organically and are messy. Refactor goals:
- Split into focused files per domain (core, user/auth, files, module-specific)
- Remove dead/commented-out code and stale `ver`/`ver_idb` constants from data structs (replaced by `__version` in store_versions.ts)
- Standardize field naming conventions
- Move sponsorships/stripe Stripe button IDs out of session store and into config
- Keep `ae_stores.ts` and `ae_events_stores.ts` as barrel re-exports for backwards compatibility
Related: `src/lib/stores/store_versions.ts` is the new home for version constants.
### [Launcher] Active features (identified 2026-03-06)
- [x] **Font size cycler (Launcher sidebar):** Font size cycler and light/dark toggle added to new `menu_launcher_controls.svelte` component; wired into `launcher_menu.svelte`. Visibility toggles (All Files / All Sessions) moved to same component and restyled to `preset-tonal-tertiary`. (2026-03-11)

View File

@@ -1,3 +1,7 @@
// store_versions MUST be first import — its side-effect wipes stale localStorage
// before svelte-persisted-store hydrates from it.
import { AE_EVENTS_LOC_VERSION } from '$lib/stores/store_versions';
import { persisted } from 'svelte-persisted-store';
import { writable } from 'svelte/store';
import type { Writable } from 'svelte/store';
@@ -12,6 +16,7 @@ const ver_idb = '2025-10-16_2139';
// Longer-term app data. This should be stored to *local* storage.
// Updated 2024-03-06
const events_local_data_struct: key_val = {
__version: AE_EVENTS_LOC_VERSION, // Schema version gate — see store_versions.ts
ver: ver,
ver_idb: ver_idb,

View File

@@ -1,3 +1,7 @@
// store_versions MUST be first import — its side-effect wipes stale localStorage
// before svelte-persisted-store hydrates from it.
import { AE_LOC_VERSION } from '$lib/stores/store_versions';
import { persisted } from 'svelte-persisted-store';
import { readable, writable } from 'svelte/store';
@@ -62,6 +66,7 @@ const ver_idb = '2025-05-01_1445'; // Not currently used
// *** BEGIN *** Longer-term app data. This should be stored to local storage.
const ae_app_local_data_defaults: key_val = {
__version: AE_LOC_VERSION, // Schema version gate — see store_versions.ts
last_page_reload: null,
// last_idb_reload: null,
// last_cache_refresh: null, // Date.now()

View File

@@ -0,0 +1,60 @@
/**
* store_versions.ts
*
* Single source of truth for persisted store schema versions.
*
* HOW IT WORKS:
* This file is imported at the top of ae_stores.ts and ae_events_stores.ts.
* ES modules resolve imports before running the importing module's body,
* so the version check + localStorage wipe here runs BEFORE persisted() is
* called — meaning stale data is cleared before svelte-persisted-store
* can hydrate from it.
*
* HOW TO USE:
* When a store schema changes in a breaking way (type change, required
* restructure, field rename that code depends on), bump the relevant version
* by 1. Any client with an older version will have that store silently wiped
* on next page load. They re-initialize from the new defaults.
*
* WHAT COUNTS AS BREAKING:
* - Changing a field's type (e.g. string → object, or null → required object)
* - Renaming a field that code reads directly (not just a display label)
* - Restructuring a nested object that breaks existing property access
* DOES NOT require a bump:
* - Adding a new optional field (svelte-persisted-store deep-merges defaults)
* - Removing a field (old value is ignored, no error)
* - Changing a default value when the stored value being stale is acceptable
*
* localStorage keys:
* 'ae_loc' → AE_LOC_VERSION
* 'ae_events_loc' → AE_EVENTS_LOC_VERSION
*/
export const AE_LOC_VERSION = 1;
export const AE_EVENTS_LOC_VERSION = 1;
// Version check side-effect: runs on import, before any persisted() call.
// Guards typeof localStorage for safety (SSR environments, test runners).
if (typeof localStorage !== 'undefined') {
_check_and_wipe('ae_loc', AE_LOC_VERSION);
_check_and_wipe('ae_events_loc', AE_EVENTS_LOC_VERSION);
}
function _check_and_wipe(key: string, expected_version: number): void {
try {
const raw = localStorage.getItem(key);
if (!raw) return; // No stored value — nothing to wipe.
const parsed = JSON.parse(raw);
if (parsed?.__version !== expected_version) {
localStorage.removeItem(key);
console.info(
`[store_versions] '${key}' wiped — schema v${parsed?.__version ?? 'none'} → v${expected_version}`
);
}
} catch {
// Corrupt JSON — wipe unconditionally.
localStorage.removeItem(key);
console.warn(`[store_versions] '${key}' wiped — corrupt JSON in localStorage`);
}
}