docs: capture today's Pres Mgmt config sync lessons for future agents
- BOOTSTRAP__AI_Agent_Quickstart.md: new category 9 under Common Mistakes pointing to today's incident (local/remote config sync pitfalls) - REFERENCE__Common_Agent_Mistakes.md: three new entries — 16) local "shadow field" silently bypasses an admin-synced master field 17) SWR await after a write does not mean dependent caches are fresh 18) conditional/stateful sync gates are effectively undebuggable - PROJECT__Stores_Svelte5_Migration.md: updated the canonical Migration Pattern example to stamp __version in the serializer (the old example silently didn't, which is exactly what caused the leads_loc/pres_mgmt_loc bugs fixed today); flagged badges_loc/launcher_loc/events_auth_loc as not yet fixed (dormant, not harmful) and idaa_loc as the next migration that should get this from day one - TODO__Agents.md: cross-referenced today's Pres Mgmt config sync overhaul in the LCI October restoration section for context, without touching the open items in that list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,10 @@ Read this section first, then open the reference doc when your task touches one
|
|||||||
6. **Network reliability** — retry classification in `api_*_object.ts` and timeout behavior.
|
6. **Network reliability** — retry classification in `api_*_object.ts` and timeout behavior.
|
||||||
7. **JSON field safety** — `*_json` null reads/writes and wrapper serialization behavior.
|
7. **JSON field safety** — `*_json` null reads/writes and wrapper serialization behavior.
|
||||||
8. **Service worker rollout behavior** — stale-tab symptoms, activation expectations, and trade-offs.
|
8. **Service worker rollout behavior** — stale-tab symptoms, activation expectations, and trade-offs.
|
||||||
|
9. **Local/remote config sync** — shadow fields that silently bypass an admin-synced master
|
||||||
|
field, SWR-await-after-write races, and stateful/conditional sync gates that desync local
|
||||||
|
state from history rather than current config. See the Pres Mgmt Config sync overhaul
|
||||||
|
(2026-06-16) in `PROJECT__AE_Events_PressMgmt_Config_Cleanup.md` for the full incident.
|
||||||
|
|
||||||
The reference doc also includes a short archive list for older, less-relevant historical incidents.
|
The reference doc also includes a short archive list for older, less-relevant historical incidents.
|
||||||
|
|
||||||
|
|||||||
@@ -72,19 +72,49 @@ export const badges_loc_defaults: BadgesLocState = { ... };
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Store file (`*.svelte.ts`)
|
### 2. Store file (`*.svelte.ts`)
|
||||||
|
|
||||||
|
**Updated 2026-06-16 — stamp `__version` in the serializer.** The original pattern below (bare
|
||||||
|
`JSON.stringify`) looked fine but has a real failure mode: `store_versions.ts`'s
|
||||||
|
`_check_and_wipe()` compares a `__version` field *inside* the stored JSON, and a bare
|
||||||
|
`JSON.stringify` never writes one. Two consequences were found in production code:
|
||||||
|
`pres_mgmt_loc`'s version bump was silently a no-op (never wired into `_check_and_wipe` at
|
||||||
|
all), and `leads_loc`'s *was* wired in, so `parsed?.__version` was always `undefined` —
|
||||||
|
always failing the comparison — wiping `ae_leads_loc` on **every single page load**. Both
|
||||||
|
fixed 2026-06-16. `badges_loc`, `launcher_loc`, and `events_auth_loc` still use the old bare
|
||||||
|
pattern below and are **not yet wired into `_check_and_wipe`** — not actively harmful (the
|
||||||
|
wipe never runs), but apply this fix before wiring any of them in.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { PersistedState } from 'runed';
|
import { PersistedState } from 'runed';
|
||||||
import { badges_loc_defaults } from './ae_events_stores__badges_defaults';
|
import { badges_loc_defaults } from './ae_events_stores__badges_defaults';
|
||||||
|
import { AE_BADGES_LOC_VERSION } from './store_versions';
|
||||||
|
|
||||||
export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults, {
|
export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults, {
|
||||||
serializer: {
|
serializer: {
|
||||||
serialize: JSON.stringify,
|
// Stamp __version on every write so store_versions.ts's _check_and_wipe() can
|
||||||
// Merge with defaults so new fields added after first session get their defaults.
|
// detect a breaking schema change and clear stale browsers on next load. This
|
||||||
deserialize: (raw: string) => ({ ...badges_loc_defaults, ...JSON.parse(raw) })
|
// import also guarantees store_versions.ts's wipe side-effect runs before this
|
||||||
|
// PersistedState reads from localStorage (ES module execution order).
|
||||||
|
serialize: (value) =>
|
||||||
|
JSON.stringify({ ...value, __version: AE_BADGES_LOC_VERSION }),
|
||||||
|
// Merge with defaults so new fields added after first session get their defaults,
|
||||||
|
// and strip __version back out so it doesn't pollute the typed state object.
|
||||||
|
deserialize: (raw: string) => {
|
||||||
|
const { __version, ...stored } = JSON.parse(raw);
|
||||||
|
return { ...badges_loc_defaults, ...stored };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then in `store_versions.ts`'s startup block:
|
||||||
|
```ts
|
||||||
|
_check_and_wipe('ae_badges_loc', AE_BADGES_LOC_VERSION);
|
||||||
|
```
|
||||||
|
|
||||||
|
See `ae_events_stores__pres_mgmt.svelte.ts` and `ae_events_stores__leads.svelte.ts` for the
|
||||||
|
two real, currently-correct examples of this pattern.
|
||||||
|
|
||||||
### 3. Consumer syntax
|
### 3. Consumer syntax
|
||||||
```ts
|
```ts
|
||||||
// Import (note .svelte extension, not .svelte.ts):
|
// Import (note .svelte extension, not .svelte.ts):
|
||||||
@@ -116,6 +146,9 @@ badges_loc.current = { ...badges_loc_defaults };
|
|||||||
1. **`idaa_loc` → PersistedState** — Highest priority for IDAA stability. Promotes `novi_uuid/verified`,
|
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.
|
`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.
|
Primary benefit: eliminates the IDAA "Access Denied" corruption from `ae_loc` bootstrap writes.
|
||||||
|
Use the `__version`-stamping serializer from the start (see Migration Pattern above) and wire
|
||||||
|
it into `_check_and_wipe()` immediately — `idaa_loc` holds auth state, so a real wipe-on-schema-
|
||||||
|
change matters more here than anywhere else this pattern has been applied so far.
|
||||||
|
|
||||||
2. **`ae_loc` → PersistedState** — Largest scope (~every route in the app). Defer until after
|
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
|
`idaa_loc` is done. Consider extracting `auth_loc` (the identity/permission fields) as the
|
||||||
|
|||||||
@@ -150,6 +150,63 @@ Review policy: balanced curation.
|
|||||||
|
|
||||||
**Verify:** After deploy, validate that long-lived tabs pick up new SW behavior as intended.
|
**Verify:** After deploy, validate that long-lived tabs pick up new SW behavior as intended.
|
||||||
|
|
||||||
|
### 16) Local "shadow field" silently bypasses the admin-synced master field
|
||||||
|
**Impact:** an admin config toggle appears to do nothing — the UI updates fine when a local
|
||||||
|
per-browser preference is clicked, but the actual admin setting has zero effect, at any
|
||||||
|
permission level. Looks like a permissions bug; isn't one.
|
||||||
|
|
||||||
|
**What happened:** A list/table column's visibility prop was computed from only a local-only,
|
||||||
|
never-synced preference field (e.g. `hide__session_li_location_field`), while a
|
||||||
|
similarly-named, admin-synced field (`hide__session_location`) existed and was correctly
|
||||||
|
synced — just never read at that particular render call site. Found twice in the same
|
||||||
|
session (Pres Mgmt POC column, then Location column).
|
||||||
|
|
||||||
|
**Rule:** When a remote config field and a local-only preference field could plausibly both
|
||||||
|
affect the same visible thing, combine them explicitly (`admin_field || local_field`) at
|
||||||
|
*every* call site that renders that thing — don't assume one supersedes the other, and don't
|
||||||
|
assume fixing it once means every other render site is also fixed.
|
||||||
|
|
||||||
|
**Verify:** `grep` every consumer of the field name (and near-miss siblings, e.g. `_li_`/`_field`
|
||||||
|
suffixed variants) before trusting that a config toggle does what its label says.
|
||||||
|
|
||||||
|
### 17) SWR `await` after a write does not mean dependent caches are fresh
|
||||||
|
**Impact:** a value you just saved appears stale or "one save behind" immediately after
|
||||||
|
saving — looks like inverted/random behavior when toggling a boolean back and forth, since
|
||||||
|
being one step behind on a 2-state toggle looks exactly like being inverted.
|
||||||
|
|
||||||
|
**What happened:** A save handler `await`ed `load_ae_obj_id__event()` (SWR) and assumed that
|
||||||
|
meant Dexie now had the fresh record. In reality the fast path returns the stale cache
|
||||||
|
immediately and refreshes Dexie via a non-awaited background fetch. A *different* page's own
|
||||||
|
sync `$effect` read Dexie before that background fetch landed, re-syncing from the stale
|
||||||
|
record and overwriting a value that had just been set correctly moments earlier.
|
||||||
|
|
||||||
|
**Rule:** After a write whose result other reactive code depends on, don't rely on an awaited
|
||||||
|
SWR reload to mean downstream caches are updated — for any call with a populated cache, it
|
||||||
|
almost never is. Update dependent local state directly from the data you already have (what
|
||||||
|
you just saved), not by waiting on a refetch. See the "try_cache: false Bug" section of
|
||||||
|
`GUIDE__SvelteKit2_Svelte5_DexieJS.md` for the related, equally counter-intuitive case where
|
||||||
|
disabling caching *also* disables the very write you wanted.
|
||||||
|
|
||||||
|
### 18) Conditional ("locked") sync gates are effectively undebuggable
|
||||||
|
**Impact:** a field's local value silently depends on the *history* of an unrelated flag's
|
||||||
|
state across past syncs, not its current value — looks like inconsistent behavior tied to
|
||||||
|
permission level, browser, or test sequence, none of which is the actual cause.
|
||||||
|
|
||||||
|
**What happened:** `sync_config__event_pres_mgmt()` only applied a subset of fields when a
|
||||||
|
separate `lock_config` flag was `true` *at that exact sync call*. Toggling `lock_config` off
|
||||||
|
even briefly during testing froze those fields at stale values in every browser that synced
|
||||||
|
during that window, with nothing in the UI indicating staleness. Removed entirely — every
|
||||||
|
event already had it set to `true` in production; the conditional gated nothing real.
|
||||||
|
|
||||||
|
**Rule:** Avoid making one config field's sync conditional on another field's *current*
|
||||||
|
value unless that's a genuine, deliberate design choice — and if it is, surface the
|
||||||
|
dependency in the UI (disable the inputs, show a warning), don't bury it in a silent
|
||||||
|
function-level gate.
|
||||||
|
|
||||||
|
**Verify:** Before adding `if (some_flag) { ...many field syncs... }`, check whether removing
|
||||||
|
the gate (sync unconditionally) loses any real behavior — query production data for whether
|
||||||
|
the gate is ever actually in the "off" state before assuming it needs to exist.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Archived Historical Items (Pruned)
|
## Archived Historical Items (Pruned)
|
||||||
|
|||||||
@@ -10,6 +10,16 @@
|
|||||||
These features regressed over the last 6 months and must be working before the LCI conference.
|
These features regressed over the last 6 months and must be working before the LCI conference.
|
||||||
Reference commit for original working implementation: `bb993a102`.
|
Reference commit for original working implementation: `bb993a102`.
|
||||||
|
|
||||||
|
**2026-06-16:** Pres Mgmt's config sync architecture was overhauled the same day this list's
|
||||||
|
LCI event data was used for live testing — `lock_config` removed (was causing "sometimes
|
||||||
|
works" reports tied to save history, not current settings), POC/Location list-table column
|
||||||
|
bugs fixed (admin setting was being silently ignored), QR display fixed to match the intended
|
||||||
|
global-default-with-trusted-override design, and the Config page got a documentation pass
|
||||||
|
(title tooltips, POC settings split into its own section). None of this touches the open
|
||||||
|
items below, but anyone picking those up should know the sync layer underneath them is in a
|
||||||
|
meaningfully different (and better-understood) state now. Full incident log:
|
||||||
|
`PROJECT__AE_Events_PressMgmt_Config_Cleanup.md`.
|
||||||
|
|
||||||
### Session POC (Champion/Moderator) — `session_view.svelte`
|
### Session POC (Champion/Moderator) — `session_view.svelte`
|
||||||
|
|
||||||
**Root cause of visible bugs:** The POC section is placed *below* the session hero card as a
|
**Root cause of visible bugs:** The POC section is placed *below* the session hero card as a
|
||||||
|
|||||||
Reference in New Issue
Block a user