docs: update docs to reflect events store migration completion

BOOTSTRAP__AI_Agent_Quickstart.md: rewrite store reactivity trap section
to distinguish events sub-stores (PersistedState, fine-grained) from
ae_loc/idaa_loc (svelte-persisted-store, coarse). Add import/read/write
syntax examples and pointer to migration doc.

PROJECT__Stores_Svelte5_Migration.md: rewritten to reflect events module
fully complete; documents established PersistedState pattern with canonical
examples; tables show all 5 sub-stores done + events_loc retired.

TODO__Agents.md: events migration marked complete (2026-06-11); idaa_loc
and ae_loc listed as remaining work; stale events_loc file_display_overrides
ref fixed.

tests/README.md: replace Leads-only store migration note with full events
sub-store table, localStorage keys, and explanation of PersistedState
deserialization (no __version guard needed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-11 16:50:06 -04:00
parent 573d20e574
commit 98e31f1528
4 changed files with 139 additions and 86 deletions

View File

@@ -1,97 +1,121 @@
# Project: Svelte 4 Store → Svelte 5 State Migration
**Status:** Execution / Phase B (In Progress)
**Priority:** High (post-April 2026 conference)
**Created:** 2026-03-30
**Related:** `TODO__Agents.md` — [Stores] Svelte 5 State Migration entry
**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 (`ae_loc`, `idaa_loc`, `ae_events_loc`, etc.) are being migrated from
Svelte 4 stores to Svelte 5 `$state` using the `runed` library's `PersistedState`. This provides
fine-grained reactivity, ensuring that effects only re-run when specific fields they access are
updated, rather than on every write to the store object.
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).
### Phase B Progress & Learnings (Updated 2026-03-30)
1. **Dependency Installed**: `runed` is now a project dependency.
2. **Module Resolution Strategy**:
- Core store files renamed: `ae_stores.ts``ae_stores.svelte.ts`, etc.
- **Critical Discovery**: SvelteKit and CLI tools (`svelte-check`) struggled to resolve
extension-less imports (like `$lib/stores/ae_stores`) when only `.svelte.ts` existed.
- **Solution**: Created `.ts` wrapper files (e.g., `src/lib/stores/ae_stores.ts`) that
simply re-export everything via `export * from './ae_stores.svelte'`. This maintains
backward compatibility for all existing import paths without manual updates.
3. **API Confirmation**: Confirmed that `runed`'s `PersistedState` uses `.current` to access
the state object, matching the intended migration syntax.
4. **Mass Replacement**:
- `$ae_loc``ae_loc_v5.current`
- `$idaa_loc``idaa_loc_v5.current`
- `$events_loc``events_loc_v5.current`
- These replacements have been applied across the entire `src/` directory (~2000+ sites).
5. **Import Updates**: A robust Python script was used to surgically add `ae_loc_v5`,
`idaa_loc_v5`, and `events_loc_v5` to existing import blocks, avoiding `import type`
lines and duplicates.
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.
---
## Syntax Changes: Before / After
## Completed: Events Module (2026-06-11)
### Store declaration
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**.
```typescript
// BEFORE (ae_stores.svelte.ts)
export const ae_loc: Writable<key_val> = persisted('ae_loc', defaults);
| 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) |
// AFTER (ae_stores.svelte.ts)
export const ae_loc_v5 = new PersistedState('ae_loc', defaults);
```
`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`.
### Reading/Writing (Consumers)
```svelte
<!-- BEFORE -->
{$ae_loc.theme_mode}
{#if $ae_loc.trusted_access}
<!-- AFTER -->
{ae_loc_v5.current.theme_mode}
{#if ae_loc_v5.current.trusted_access}
```
### Batch Update Pattern
```typescript
// Use Object.assign for multi-field updates to maintain fine-grained reactivity
Object.assign(ae_loc_v5.current, { theme_mode: 'dark', edit_mode: true });
```
`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.
---
## Implementation Notes
## In Progress / Remaining: `ae_stores.ts` and `ae_idaa_stores.ts`
### Prettier & Formatting
Because the mass migration touches ~250 files, formatting may be inconsistent (especially in
import blocks). It is recommended to run `npm run format` (if available) or rely on standard
linting after the imports are stabilized.
Both stores had their unused default properties pruned (2026-06-11), reducing migration scope:
### Batching vs. "Big Sweep"
While the original plan suggested smaller batches, the global nature of `ae_loc` and the
hundreds of interdependent files made a "Big Sweep" more efficient to ensure the app remains
in a consistent state. However, verification should still be done module-by-module.
**`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`
---
## Current Status (Pause Point)
## Migration Pattern (Established)
- [x] Phase A: Plan written.
- [x] Phase B: Core infrastructure setup (runed, renames, wrappers).
- [x] Phase B: Mass variable replacement ($ae_loc -> ae_loc_v5.current).
- [/] Phase B: Mass import update (In Progress/Verifying).
- [ ] Phase B: Final validation (`svelte-check` clean).
Each sub-store follows this pattern, using the `badges` store as the canonical reference:
**Do NOT stage or commit until `npx svelte-check` is fully verified.**
The app currently has a high error count due to the transition period where imports are being
re-aligned. Final verification is the next step after the pause.
### 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.