From 98e31f152821a2fa2bbb7876a15b6f59b8dee40b Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 11 Jun 2026 16:50:06 -0400 Subject: [PATCH] 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 --- .../BOOTSTRAP__AI_Agent_Quickstart.md | 26 ++- .../PROJECT__Stores_Svelte5_Migration.md | 168 ++++++++++-------- documentation/TODO__Agents.md | 17 +- tests/README.md | 14 +- 4 files changed, 139 insertions(+), 86 deletions(-) diff --git a/documentation/BOOTSTRAP__AI_Agent_Quickstart.md b/documentation/BOOTSTRAP__AI_Agent_Quickstart.md index 3f170d97..f5528548 100644 --- a/documentation/BOOTSTRAP__AI_Agent_Quickstart.md +++ b/documentation/BOOTSTRAP__AI_Agent_Quickstart.md @@ -142,19 +142,31 @@ onDestroy(() => cleanup()); - Use `$state()` for local component state with no external binding. ### Store reactivity trap (important for `$effect`) -The app uses `svelte-persisted-store` (Svelte 4 contract) for `$ae_loc`, `$ae_api`, -`$ae_sess`, etc. In Svelte 5 `$effect`, reading **any field** of a Svelte 4 store -subscribes to the **entire store**. This means unrelated writes to `$ae_loc` -(e.g. iframe height, SWR reload) will re-trigger your effect. Be conservative about -what you read from these stores inside `$effect` blocks. See `PROJECT__Stores_Svelte5_Migration.md` -for the long-term fix plan. +The app has two kinds of persisted stores — know which you're reading: -For search pages specifically, this usually means: +**Svelte 4 `svelte-persisted-store` (coarse reactivity) — still used for:** +- `$ae_loc`, `$ae_sess`, `$ae_api` (global app state) +- `$idaa_loc`, `$idaa_sess` (IDAA module) + +In Svelte 5 `$effect`, reading **any field** of these stores subscribes to the **entire store**. +Unrelated writes to `$ae_loc` (e.g. iframe height, SWR reload) will re-trigger your effect. +Be conservative about what you read from these stores inside `$effect` blocks. + +**Svelte 5 `PersistedState` (fine-grained reactivity) — Events module stores:** +- `badges_loc`, `leads_loc`, `pres_mgmt_loc`, `launcher_loc`, `events_auth_loc` + +These use `runed`'s `PersistedState`. Access via `.current` (no `$` sigil): +`badges_loc.current.field`. Writing one field only re-triggers effects that read that field. +Import from the `.svelte` extension: `import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'`. + +For search pages using the coarse stores, this usually means: - keep true user preferences in persisted local state - keep transient triggers, loading flags, and last-executed search keys in session state when possible - let the page effect schedule the search, but put the duplicate-execution guard inside the search executor so page-load auto-search still runs after hydration - if the search text or filters are mirrored from localStorage on mount, expect that mount-time writes can re-trigger the effect unless the executor has its own guard +See `PROJECT__Stores_Svelte5_Migration.md` for migration status and the pattern to follow when migrating remaining stores. + ### `{#await}` blocks ```svelte {#await somePromise} diff --git a/documentation/PROJECT__Stores_Svelte5_Migration.md b/documentation/PROJECT__Stores_Svelte5_Migration.md index 8f8e7261..e38c2e0d 100644 --- a/documentation/PROJECT__Stores_Svelte5_Migration.md +++ b/documentation/PROJECT__Stores_Svelte5_Migration.md @@ -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 = 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 - -{$ae_loc.theme_mode} -{#if $ae_loc.trusted_access} - - -{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. diff --git a/documentation/TODO__Agents.md b/documentation/TODO__Agents.md index cb6bdae9..ab68e5da 100644 --- a/documentation/TODO__Agents.md +++ b/documentation/TODO__Agents.md @@ -33,12 +33,17 @@ Finalizing the 100% adoption of V3 Standard endpoints and retirement of legacy w ### [Stores] Svelte 4 → Svelte 5 State Migration The app uses `svelte-persisted-store` (coarse reactivity). Migration target: replace with Svelte 5 -`$state`-based persistence for fine-grained updates. +`PersistedState` (from `runed`) for fine-grained updates. See `PROJECT__Stores_Svelte5_Migration.md`. -- [ ] **Phase A — Project plan + wrapper decision:** Write `PROJECT__Stores_Svelte5_Migration.md`. -- [ ] **Phase B — Core auth stores (highest impact):** `ae_loc`, `idaa_loc`. -- [ ] **Phase C — Remaining persisted stores:** `ae_api`, `ae_events_stores`. -- [ ] **Phase D — Non-persisted writable stores:** `ae_sess`, `slct`, `ae_snip`, etc. +- [x] **Events module — COMPLETE (2026-06-11):** `events_loc` fully retired. All 5 sub-stores + (`badges_loc`, `leads_loc`, `pres_mgmt_loc`, `launcher_loc`, `events_auth_loc`) are on + `PersistedState`. Unused fields also pruned from `ae_stores.ts` and `ae_idaa_stores.ts`. +- [ ] **`idaa_loc` → PersistedState** — Highest remaining priority. Root cause of the IDAA + "Access Denied" corruption bug (`ae_loc` bootstrap writes stomp on `authenticated_access`). + Promote `novi_*` identity fields and `archives/bb/recovery_meetings` sub-objects. +- [ ] **`ae_loc` → PersistedState** — Largest scope. Extract `auth_loc` sub-store first + (the identity/permission fields are what get corrupted). Defer full migration until after `idaa_loc`. +- [ ] **Non-persisted writables** (`ae_sess`, `slct`, etc.) — Low priority; no coarse-reactivity problem. ### [Data Layer] IDB sorting + content version rollout Sorting baseline is now `build_tmp_sort` (ASC chain, no `.reverse()` on tmp-sort lists). @@ -76,7 +81,7 @@ uses `build_tmp_sort` (overrides generic encoding in its `specific_processor`). ## ⚙️ DevOps & Backend - [ ] **[Backend] `event_file` — add `cfg_json` column (post-CMSC)** — The per-file display - override currently uses a localStorage workaround (`$events_loc.launcher.file_display_overrides`) + override currently uses a localStorage workaround (`launcher_loc.current.file_display_overrides`) because `event_file` has no JSON blob column. Proper fix: add `cfg_json` to the `event_file` DB table, expose it through the FastAPI model, then migrate the frontend back to reading/writing the backend field (restoring global/cross-device persistence). Frontend code is in diff --git a/tests/README.md b/tests/README.md index 29fc8c70..d9964936 100644 --- a/tests/README.md +++ b/tests/README.md @@ -35,7 +35,19 @@ Shared test helpers (`tests/_helpers/`) | `minimal_ae_api_mocks.ts` | `attach_minimal_routes()`, `seed_trusted_session()`, `setup_badge_test_page()` | | `leads_helpers.ts` | `setup_leads_test_page()`, `seed_events_loc()`, `seed_ae_loc()`, `attach_leads_routes()`, `minimal_exhibit()`, `minimal_tracking()` — Leads module test helpers | -Note: After the Leads persisted-store migration, tests that seed localStorage should also seed the new `leads_loc` defaults and include the expected `__version` values (see `src/lib/stores/store_versions.ts`) to avoid store wipe behavior during test startup. Update `tests/_helpers/leads_helpers.ts` accordingly. +Note: All five Events sub-stores now use `PersistedState` with their own localStorage keys. `ae_events_loc` no longer exists — do not seed it. Tests that exercise Events module features may need to seed any of the following keys: + +| Store | localStorage key | Defaults file | +| --- | --- | --- | +| `badges_loc` | `ae_badges_loc` | `ae_events_stores__badges_defaults.ts` | +| `leads_loc` | `ae_leads_loc` | `ae_events_stores__leads_defaults.ts` | +| `pres_mgmt_loc` | `ae_pres_mgmt_loc` | `ae_events_stores__pres_mgmt_defaults.ts` | +| `launcher_loc` | `ae_launcher_loc` | `ae_events_stores__launcher_defaults.ts` | +| `events_auth_loc` | `ae_events_auth_loc` | `ae_events_stores__auth.svelte.ts` (inline defaults) | + +`PersistedState` stores do **not** use a `__version` guard like `ae_loc`. They use spread-merge deserialization (`{ ...defaults, ...JSON.parse(raw) }`), so new fields get their defaults automatically. No version stamp is needed when seeding these stores. + +`ae_loc` still uses `svelte-persisted-store` with a `__version` guard — seed it as before (see the `__version` lesson below). **`setup_badge_test_page(page, event_id)`** is the one-call `beforeEach` for any badge/event print page test. It wires the pageerror listener, all V3 API mocks, and the trusted auth localStorage seed in one call.