# Project: Svelte 4 Store → Svelte 5 State Migration **Status:** Events module — COMPLETE. Core / IDAA — In Progress (field cleanup done, PersistedState pending). **Priority:** High **Created:** 2026-03-30 | **Last updated:** 2026-06-11 --- ## Background All core Aether stores were built with `svelte-persisted-store` (Svelte 4 contract). This provides coarse reactivity: any write to any field notifies *all* subscribers and re-serializes the entire object. For large stores like `ae_loc` and `ae_events_loc`, this caused unnecessary re-renders and was the root cause of the IDAA "Access Denied" corruption bug (a bootstrap write to `ae_loc` would overwrite `authenticated_access` if a persisted value was slightly different, corrupting IDAA member state stored in the same key). The migration target: replace all `persisted()` stores with `runed`'s `PersistedState`, which uses Svelte 5 fine-grained reactivity — a write to one field only triggers effects that read that field. --- ## Completed: Events Module (2026-06-11) All `ae_events_stores` sub-modules have been promoted to their own `PersistedState` stores and `events_loc` (the old `persisted()` store) has been **fully retired**. | Store | File | localStorage key | Status | |---|---|---|---| | `badges_loc` | `ae_events_stores__badges.svelte.ts` | `ae_badges_loc` | ✅ Done (2026-04-02) | | `leads_loc` | `ae_events_stores__leads.svelte.ts` | `ae_leads_loc` | ✅ Done (2026-04-03) | | `pres_mgmt_loc` | `ae_events_stores__pres_mgmt.svelte.ts` | `ae_pres_mgmt_loc` | ✅ Done (2026-04-03) | | `launcher_loc` | `ae_events_stores__launcher.svelte.ts` | `ae_launcher_loc` | ✅ Done (2026-06-11) | | `events_auth_loc` | `ae_events_stores__auth.svelte.ts` | `ae_events_auth_loc` | ✅ Done (2026-06-11) | | `events_loc` | *(retired)* | `ae_events_loc` | ✅ Store removed (2026-06-11) | `ae_events_stores.ts` now only exports `events_sess` (in-memory writable, no migration needed), `events_slct`, `events_trig`, `events_trig_kv`, `events_trigger`, and the `EVENTS_MODULE_TITLE` constant. The file no longer imports `svelte-persisted-store`. `store_versions.ts` still calls `_check_and_wipe('ae_events_loc', AE_EVENTS_LOC_VERSION)` to clean old data out of users' browsers — this is intentional and should be kept for at least one year. --- ## In Progress / Remaining: `ae_stores.ts` and `ae_idaa_stores.ts` Both stores had their unused default properties pruned (2026-06-11), reducing migration scope: **`ae_loc`** (in `ae_stores.ts`) — still `persisted('ae_loc', ...)`: - Remaining fields: auth/identity, theme, permissions, ui config, file upload tracking, query prefs - This is the highest-impact remaining migration — used in nearly every route - Root cause of IDAA "Access Denied" bug (coarse write during bootstrap stomps on permission fields) **`idaa_loc`** (in `ae_idaa_stores.ts`) — still `persisted('ae_idaa_loc', ...)`: - Remaining fields: `novi_uuid/verified/ts`, `novi_admin_li/trusted_li`, `archives/bb/recovery_meetings` sub-objects - Fields pruned (2026-06-11): `ds`, `idaa_cfg_json`, top-level `qry__*`, `novi_*_base_url`, `novi_rate_limited_until` --- ## Migration Pattern (Established) Each sub-store follows this pattern, using the `badges` store as the canonical reference: ### 1. Defaults file (`*_defaults.ts`) Define the shape and defaults as a plain TypeScript object (and interface if complex): ```ts export interface BadgesLocState { ... } export const badges_loc_defaults: BadgesLocState = { ... }; ``` ### 2. Store file (`*.svelte.ts`) ```ts import { PersistedState } from 'runed'; import { badges_loc_defaults } from './ae_events_stores__badges_defaults'; export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults, { serializer: { serialize: JSON.stringify, // Merge with defaults so new fields added after first session get their defaults. deserialize: (raw: string) => ({ ...badges_loc_defaults, ...JSON.parse(raw) }) } }); ``` ### 3. Consumer syntax ```ts // Import (note .svelte extension, not .svelte.ts): import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'; // Read: badges_loc.current.fulltext_search_qry_str // Write (fine-grained — only triggers effects that read this field): badges_loc.current.fulltext_search_qry_str = 'hello'; // Bulk reset: badges_loc.current = { ...badges_loc_defaults }; ``` ### Key differences from old `svelte-persisted-store` pattern: | | Old (`persisted()`) | New (`PersistedState`) | |---|---|---| | Import | `import { events_loc } from '...ae_events_stores'` | `import { badges_loc } from '...ae_events_stores__badges.svelte'` | | Read | `$events_loc.badges.field` | `badges_loc.current.field` | | Write | `$events_loc.badges.field = x` | `badges_loc.current.field = x` | | Reactivity | Coarse — entire store notified | Fine-grained — only affected fields | | In `$effect` | Subscribes to entire store | Only subscribes to fields you read | --- ## Next Steps 1. **`idaa_loc` → PersistedState** — Highest priority for IDAA stability. Promotes `novi_uuid/verified`, `archives`, `bb`, `recovery_meetings` sub-objects to their own stores following the same pattern. Primary benefit: eliminates the IDAA "Access Denied" corruption from `ae_loc` bootstrap writes. 2. **`ae_loc` → PersistedState** — Largest scope (~every route in the app). Defer until after `idaa_loc` is done. Consider extracting `auth_loc` (the identity/permission fields) as the first sub-store since those are the fields implicated in the IDAA corruption bug.