fix(pres_mgmt): config sync round 2 — save race, dead fields, launcher gate, version stamps
Four fixes found while tracing why Manager-saved Config page changes (QR, POC column, etc.) weren't reliably reaching pres_mgmt_loc: 1. Config page save was a race, not deterministic. The save handler only called load_ae_obj_id__event() (SWR — returns stale Dexie cache immediately, refreshes in the background, not awaited) and assumed that "picked up the new config." It never called sync_config__event_pres_mgmt() itself. Now calls it directly with the just-saved draft, so the editing browser updates instantly with no race. Kept the load_ae_obj_id__event() call (default try_cache: true) for propagating to other browsers/tabs via Dexie — do not pass try_cache: false there, that skips the Dexie write entirely. 2. Removed the dead "Lock Config" Sync/Unlink toggle in the sign-in panel (e_app_access_type.svelte). It wrote to four fields ($ae_loc.lock_config/sync_local_config, pres_mgmt_loc.current.lock_config/sync_local_config) that are never read anywhere (confirmed via full-repo grep), and confusingly shared a name with the real, functional "Lock Config" checkbox on the Pres Mgmt Config page. Removed the button and the now-orphaned lock_config/sync_local_config fields from PresMgmtLocState. 3. show__launcher_link was never assigned by sync_config__event_pres_mgmt() — only its inverse hide__launcher_link was. The toggle button's `show__launcher_link || trusted_access` visibility gate (in 3 menu files) always collapsed to trusted-only, ignoring the admin's setting. Added the missing assignment. 4. AE_PRES_MGMT_LOC_VERSION was bumped to 2 this morning claiming it "forces a localStorage reset" — it didn't, because _check_and_wipe() was never wired up for ae_pres_mgmt_loc, and even if it had been, the store never wrote a __version field to compare. Fixed: the store's serializer now stamps __version, and store_versions.ts wires the check. Found and fixed the same bug already live in ae_leads_loc, except worse there — it was wiping leads users' local prefs on EVERY page load, not just once. All logged in PROJECT__AE_Events_PressMgmt_Config_Cleanup.md. svelte-check: 0 errors, 0 warnings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -214,15 +214,33 @@ Safe and backward compatible — old DB records fall through to `?? false` defau
|
||||
|
||||
### Regression Fixes Needed (2026-06-12 Audit)
|
||||
|
||||
- [ ] **Add `show__launcher_link_legacy` to `PressMgmtRemoteCfg`** or remove entirely if deprecated
|
||||
- Currently hard-coded to `true` in sync function (line 1054 `ae_events__event.ts`)
|
||||
- Can't be controlled via config UI
|
||||
- [ ] **Resolve `hide__launcher_link*` local/remote conflict**
|
||||
- Menu toggles ([ae_comp__events_menu_opts.svelte](../src/routes/events/ae_comp__events_menu_opts.svelte) lines 462-494) use `hide__launcher_link` for LOCAL UI state
|
||||
- Remote schema uses `show__launcher_link` (inverted)
|
||||
- Decision: Keep separate? Document clearly? Unify?
|
||||
- [ ] **Add `AE_PRES_MGMT_LOC_VERSION` to `store_versions.ts`** (Step 2 requirement)
|
||||
- [ ] **Clean `hide__launcher_link*` from defaults** if truly deprecated (lines 154-155, 333-334 in `pres_mgmt_defaults.ts`)
|
||||
- [x] **`hide__launcher_link_legacy` removed entirely** (other agent's "config schema cleanup
|
||||
phase 2" commit, 2026-06-16) — Flask launcher is fully retired, no longer hard-coded or
|
||||
present anywhere in `PressMgmtRemoteCfg` / `PresMgmtLocState` / the sync function.
|
||||
- [x] **`hide__launcher_link*` / `show__launcher_link` local/remote conflict resolved
|
||||
(2026-06-16)** — kept separate (they serve different purposes: `hide__launcher_link`
|
||||
gates the launcher link *content*, `show__launcher_link` gates the manual toggle
|
||||
*button*'s visibility), but `show__launcher_link` was never actually assigned by
|
||||
`sync_config__event_pres_mgmt()` — only its inverse `hide__launcher_link` was. So the
|
||||
toggle button's `show__launcher_link || trusted_access` gate (in
|
||||
`ae_comp__events_menu_opts.svelte`, `event_page_menu.svelte`,
|
||||
`location_page_menu.svelte`) always collapsed to trusted-only, ignoring the admin's
|
||||
setting. Added the missing `loc.show__launcher_link = ...` assignment right next to
|
||||
`hide__launcher_link` in the lock-synced block.
|
||||
- [x] **`AE_PRES_MGMT_LOC_VERSION` properly wired into `store_versions.ts` (2026-06-16)**
|
||||
— the other agent's commit bumped this constant to 2 claiming it "forces a localStorage
|
||||
reset," but `_check_and_wipe()` was never actually called for `ae_pres_mgmt_loc`, and
|
||||
even if it had been, the store's serializer never wrote a `__version` field for it to
|
||||
compare against — so the bump was a complete no-op. Fixed: `ae_events_stores__pres_mgmt.svelte.ts`'s
|
||||
custom serializer now stamps `__version` on every write, and `store_versions.ts` calls
|
||||
`_check_and_wipe('ae_pres_mgmt_loc', AE_PRES_MGMT_LOC_VERSION)`. Side effect: every
|
||||
browser's existing `ae_pres_mgmt_loc` (no `__version` ever written before) will wipe
|
||||
once on next load and resync clean from the remote config — this is expected and fine.
|
||||
**Found the same bug already live in `ae_leads_loc`** (actively wiping leads users' local
|
||||
prefs on *every* page load, not just once) and fixed it the same way — see
|
||||
`ae_events_stores__leads.svelte.ts`. `badges_loc`/`launcher_loc`/`events_auth_loc` have
|
||||
version constants declared but not wired into `_check_and_wipe()` at all (dormant, not
|
||||
actively harmful) — not fixed, flagged for whoever picks that up next.
|
||||
- [x] **POC column local/remote conflict fixed (2026-06-16)** — `show__session_li_poc_field` was
|
||||
local-only (never synced) and the session-list-table prop computation ignored the admin's
|
||||
`hide__session_poc` master switch entirely. Fixed: added `show__session_li_poc_field` to
|
||||
@@ -259,6 +277,34 @@ Safe and backward compatible — old DB records fall through to `?? false` defau
|
||||
`locations/+page.svelte`, `location/[event_location_id]/+page.svelte`, and
|
||||
`reports/+page.svelte`. Any new pres_mgmt page that can be a first-load entry point
|
||||
(i.e. not always reached via `/pres_mgmt` or `/session/[id]` first) needs this same block.
|
||||
- [x] **Config page save was a race, not deterministic (2026-06-16)** — after PATCHing
|
||||
`mod_pres_mgmt_json`, the save handler only called `load_ae_obj_id__event()` (SWR —
|
||||
returns the stale Dexie cache immediately, refreshes from the API in the background,
|
||||
*not awaited*) and assumed that "picked up the new config." It never actually called
|
||||
`sync_config__event_pres_mgmt()` itself. Whether the editor's own browser reflected the
|
||||
change depended entirely on winning a race against an un-awaited background fetch —
|
||||
explains why specific just-changed fields (QR, POC column, profile-pic visibility, one
|
||||
report key) intermittently looked stale even to the admin who just saved them, while
|
||||
older unchanged fields stayed correct. Fixed: the save handler now calls
|
||||
`sync_config__event_pres_mgmt({ pres_mgmt_cfg_remote: draft })` directly with the
|
||||
just-saved draft, so the editing browser updates instantly with no race. (Kept the
|
||||
`load_ae_obj_id__event()` call too, with its default `try_cache: true` — that's what
|
||||
propagates the fresh record to Dexie for *other* browsers/tabs. Do not pass
|
||||
`try_cache: false` there — that skips the Dexie write entirely, see the documented
|
||||
"try_cache: false Bug" in `GUIDE__SvelteKit2_Svelte5_DexieJS.md`.)
|
||||
- [x] **Removed dead "Lock Config" Sync/Unlink toggle (2026-06-16)** — a Manager-only
|
||||
button in the sign-in panel (`e_app_access_type.svelte`) wrote to
|
||||
`$ae_loc.lock_config`/`sync_local_config` and `pres_mgmt_loc.current.lock_config`/
|
||||
`sync_local_config`. Confirmed via full-repo grep that none of those four fields are
|
||||
read anywhere. It also confusingly shared the name "Lock Config" with the real,
|
||||
functional checkbox on the Pres Mgmt Config page (`draft.lock_config`, part of
|
||||
`PressMgmtRemoteCfg`, which actually gates `sync_config__event_pres_mgmt()`'s
|
||||
remote→local sync). Removed the button and the now-fully-orphaned
|
||||
`lock_config`/`sync_local_config` fields from `PresMgmtLocState`. Left
|
||||
`$ae_loc.lock_config`/`sync_local_config` (the general app store) alone — `lock_config`
|
||||
was never even in `ae_loc`'s declared defaults (a phantom field created ad-hoc by the
|
||||
dead button), and `ae_loc.sync_local_config` is out of scope for a pres_mgmt-only pass;
|
||||
defer to the planned `ae_loc` migration in `PROJECT__Stores_Svelte5_Migration.md`.
|
||||
|
||||
### Step 6 scope (mechanical find-replace)
|
||||
|
||||
|
||||
@@ -1060,6 +1060,12 @@ export function sync_config__event_pres_mgmt({
|
||||
// Launcher links (show__ in remote → invert to hide__ in local display state)
|
||||
loc.hide__launcher_link =
|
||||
!(pres_mgmt_cfg_remote?.show__launcher_link ?? false);
|
||||
// Mirror the raw remote flag too — the toggle BUTTON's own visibility (not the
|
||||
// launcher link content itself) is gated on show__launcher_link directly in
|
||||
// ae_comp__events_menu_opts.svelte / event_page_menu.svelte / location_page_menu.svelte.
|
||||
// This was never assigned before, so that gate always collapsed to trusted_access-only.
|
||||
loc.show__launcher_link =
|
||||
pres_mgmt_cfg_remote?.show__launcher_link ?? false;
|
||||
|
||||
// Navigation / UI constraints
|
||||
loc.limit__navigation =
|
||||
|
||||
@@ -8,12 +8,10 @@ import { afterNavigate } from '$app/navigation';
|
||||
import {
|
||||
Lock,
|
||||
LockOpen,
|
||||
RefreshCw,
|
||||
ShieldEllipsis,
|
||||
ShieldMinus,
|
||||
ShieldPlus,
|
||||
ShieldUser,
|
||||
Unlink,
|
||||
User,
|
||||
UserCheck,
|
||||
UserRound,
|
||||
@@ -29,8 +27,6 @@ import {
|
||||
slct_trigger
|
||||
} from '$lib/stores/ae_stores';
|
||||
// import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
// Ideally the Event related stores should not be imported here?
|
||||
import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte';
|
||||
// import { db_events } from "$lib/db_events";
|
||||
|
||||
// export let hidden: boolean = false;
|
||||
@@ -361,47 +357,14 @@ function handle_clear_access() {
|
||||
|
||||
<div class="transition-all">
|
||||
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}
|
||||
{#if $ae_loc.manager_access}
|
||||
{#if $ae_loc?.sync_local_config}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.sync_local_config = false;
|
||||
pres_mgmt_loc.current.sync_local_config = false;
|
||||
|
||||
$ae_loc.lock_config = false;
|
||||
pres_mgmt_loc.current.lock_config = false;
|
||||
|
||||
// dispatch_sync_local_config_changed();
|
||||
// tick();
|
||||
return false;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-success border-success-500 hover:preset-filled-success-500 border transition-all hover:transition-all *:hover:inline"
|
||||
title="Syncing the local configuration with the remote configuration.">
|
||||
<RefreshCw size="1em" class="m-1" />
|
||||
<span class="hidden"> Sync </span>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$ae_loc.sync_local_config = true;
|
||||
pres_mgmt_loc.current.sync_local_config = true;
|
||||
|
||||
$ae_loc.lock_config = true;
|
||||
pres_mgmt_loc.current.lock_config = true;
|
||||
|
||||
// dispatch_sync_local_config_changed();
|
||||
// tick();
|
||||
return true;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-warning border-warning-500 hover:preset-filled-warning-500 border transition-all hover:transition-all *:hover:inline"
|
||||
title="Currently not syncing with the remote server. Re-sync the local configuration with the remote configuration?">
|
||||
<Unlink size="1em" class="m-1" />
|
||||
<span class="hidden"> Re-sync? </span>
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- Removed 2026-06-16: dead "Lock Config" Sync/Unlink toggle. It wrote to
|
||||
$ae_loc.sync_local_config/lock_config and pres_mgmt_loc.current.sync_local_config/
|
||||
lock_config, but nothing in the codebase ever read any of those four fields —
|
||||
confirmed via full-repo grep. It also confusingly shared a name with the real,
|
||||
functional "Lock Config" checkbox on the Pres Mgmt Config page (which gates
|
||||
sync_config__event_pres_mgmt()'s remote->local sync via the value actually
|
||||
stored in event.mod_pres_mgmt_json.lock_config, not this local mirror).
|
||||
See PROJECT__AE_Events_PressMgmt_Config_Cleanup.md. -->
|
||||
|
||||
<!-- {#if $ae_loc.edit_mode}
|
||||
<button
|
||||
|
||||
@@ -17,10 +17,22 @@
|
||||
*/
|
||||
import { PersistedState } from 'runed';
|
||||
import { leads_loc_defaults } from './ae_events_stores__leads_defaults';
|
||||
import { AE_LEADS_LOC_VERSION } from './store_versions';
|
||||
|
||||
export const leads_loc = new PersistedState('ae_leads_loc', leads_loc_defaults, {
|
||||
serializer: {
|
||||
serialize: JSON.stringify,
|
||||
deserialize: (raw: string) => ({ ...leads_loc_defaults, ...JSON.parse(raw) })
|
||||
// Stamp __version on every write so store_versions.ts's _check_and_wipe() can
|
||||
// detect a breaking schema change and clear stale browsers on next load. Found
|
||||
// 2026-06-16: this was previously bare JSON.stringify with no __version field,
|
||||
// which made _check_and_wipe('ae_leads_loc', ...) see undefined !== expected
|
||||
// every time and wipe this store on EVERY page load. This 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_LEADS_LOC_VERSION }),
|
||||
deserialize: (raw: string) => {
|
||||
const { __version, ...stored } = JSON.parse(raw);
|
||||
return { ...leads_loc_defaults, ...stored };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,10 +14,19 @@
|
||||
*/
|
||||
import { PersistedState } from 'runed';
|
||||
import { pres_mgmt_loc_defaults } from './ae_events_stores__pres_mgmt_defaults';
|
||||
import { AE_PRES_MGMT_LOC_VERSION } from './store_versions';
|
||||
|
||||
export const pres_mgmt_loc = new PersistedState('ae_pres_mgmt_loc', pres_mgmt_loc_defaults, {
|
||||
serializer: {
|
||||
serialize: JSON.stringify,
|
||||
deserialize: (raw: string) => ({ ...pres_mgmt_loc_defaults, ...JSON.parse(raw) })
|
||||
// Stamp __version on every write so store_versions.ts's _check_and_wipe() can
|
||||
// detect a breaking schema change and clear stale browsers on next load. This
|
||||
// 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_PRES_MGMT_LOC_VERSION }),
|
||||
deserialize: (raw: string) => {
|
||||
const { __version, ...stored } = JSON.parse(raw);
|
||||
return { ...pres_mgmt_loc_defaults, ...stored };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,10 +86,6 @@ export interface PressMgmtRemoteCfg {
|
||||
// Fields synced from remote are overwritten on every event load when lock_config=true.
|
||||
// ---------------------------------------------------------------------------
|
||||
export interface PresMgmtLocState {
|
||||
// --- System / lock state (mirrored from remote for display) ---
|
||||
sync_local_config: boolean;
|
||||
lock_config: boolean;
|
||||
|
||||
// --- Query / search preferences ---
|
||||
use_12h: boolean;
|
||||
qry_enabled: 'all' | 'not_enabled' | 'enabled';
|
||||
@@ -268,9 +264,6 @@ export interface PresMgmtSessState {
|
||||
|
||||
// Persisted pres_mgmt local config — survives browser sessions.
|
||||
export const pres_mgmt_loc_defaults: PresMgmtLocState = {
|
||||
// System / lock state
|
||||
sync_local_config: false,
|
||||
lock_config: false,
|
||||
|
||||
// Query / search
|
||||
use_12h: true,
|
||||
|
||||
@@ -120,11 +120,16 @@ if (
|
||||
_check_and_wipe('ae_loc', AE_LOC_VERSION);
|
||||
_check_and_wipe('ae_events_loc', AE_EVENTS_LOC_VERSION);
|
||||
_check_and_wipe('ae_idaa_loc', AE_IDAA_LOC_VERSION);
|
||||
// ae_leads_loc (PersistedState, runed) stamps __version itself in its custom
|
||||
// serializer — see ae_events_stores__leads.svelte.ts. FIXED 2026-06-16: this was
|
||||
// previously bare JSON.stringify with no __version field, so this check always saw
|
||||
// undefined !== expected and wiped ae_leads_loc on EVERY page load.
|
||||
_check_and_wipe('ae_leads_loc', AE_LEADS_LOC_VERSION);
|
||||
// ae_pres_mgmt_loc uses PersistedState (runed) which stores raw JSON without a __version
|
||||
// field. The _check_and_wipe mechanism requires __version in the stored data — do NOT
|
||||
// add it here until pres_mgmt_loc_defaults includes __version. For now the key is new
|
||||
// (no stale data exists) so no wipe is needed.
|
||||
// ae_pres_mgmt_loc (PersistedState, runed) stamps __version itself in its custom
|
||||
// serializer — see ae_events_stores__pres_mgmt.svelte.ts. Importing that module runs
|
||||
// this file's side effects first (ES module order), so this check sees real stored
|
||||
// data, not a race against the PersistedState constructor.
|
||||
_check_and_wipe('ae_pres_mgmt_loc', AE_PRES_MGMT_LOC_VERSION);
|
||||
}
|
||||
|
||||
function _check_and_wipe(key: string, expected_version: number): void {
|
||||
|
||||
@@ -147,7 +147,23 @@ async function save() {
|
||||
fields: { mod_pres_mgmt_json: draft },
|
||||
log_lvl: 1
|
||||
});
|
||||
// Reload the event so the sync function picks up the new config
|
||||
|
||||
// Sync this browser's pres_mgmt_loc immediately from the just-saved draft.
|
||||
// WHY: load_ae_obj_id__event() below is SWR — it returns the stale Dexie
|
||||
// cache right away and refreshes from the API in the background (fire and
|
||||
// forget), so it cannot be relied on to update pres_mgmt_loc in time. Calling
|
||||
// the sync function directly here makes the editor's own browser update
|
||||
// deterministically, with no race against Dexie/liveQuery propagation.
|
||||
events_func.sync_config__event_pres_mgmt({
|
||||
pres_mgmt_cfg_remote: draft,
|
||||
log_lvl: 1
|
||||
});
|
||||
|
||||
// Also kick off the normal SWR reload (try_cache: true, the default) so
|
||||
// Dexie gets the fresh record in the background — this is what lets OTHER
|
||||
// browsers/tabs pick up the change next time they query Dexie. Do not pass
|
||||
// try_cache: false here — that skips the Dexie write entirely (see
|
||||
// GUIDE__SvelteKit2_Svelte5_DexieJS.md "try_cache: false Bug").
|
||||
await events_func.load_ae_obj_id__event({
|
||||
api_cfg: $ae_api,
|
||||
event_id: event_id,
|
||||
|
||||
Reference in New Issue
Block a user