# Project: Pres Mgmt Config Cleanup & Config UI **Status:** 🟑 ~70% Complete β€” Core Working, Cleanup Pass Needed **Priority:** Medium (core features functional; remaining work is deprecation + consistency) **Created:** 2026-04-02 **Last Updated:** 2026-06-12 (regression audit) **Related:** `TODO__Agents.md`, `PROJECT__Stores_Svelte5_Migration.md` --- ## Background The `event.mod_pres_mgmt_json` config grew organically across several conferences (LCI, BGH, etc.) and has accumulated serious inconsistencies: - Mixed `show__` and `hide__` prefixes for the same concepts - Some features have BOTH `show__foo` and `hide__foo` keys active simultaneously - Duplicate keys with different names (`file_purpose_option_kv` = `file_purpose_option_li`) - Dead config (`HOLD__*` prefix) - Type inconsistency (`label__person_external_id: false` vs `"LCI member ID"` string) - Keys in the DB not consumed by `sync_config__event_pres_mgmt()` - Bug: `label__session_poc_name_short` is read then immediately overwritten (line 970-972 in ae_events__event.ts) - `hide_launcher_link` / `hide_launcher_link_legacy` missing the `__` separator (inconsistent) - `show_content__presentation_description` uses a third naming convention - Admin must edit DB records directly to change config β€” error-prone The local config (`events_loc.pres_mgmt`) is also tangled into the main `events_loc` persisted store which is part of the paused Svelte 5 migration. --- ## Goals 1. **Canonical config schema** β€” define a TypeScript interface for `mod_pres_mgmt_json` 2. **Consistent naming convention** β€” one rule for all `show__`/`hide__` keys 3. **New Svelte 5 store** β€” break out local pres_mgmt config from `events_loc` 4. **Config UI** β€” admin page within pres_mgmt to manage the remote config 5. **No more direct DB edits** for routine pres_mgmt configuration --- ## Convention Decision **Rule: the prefix reflects the default state.** | Prefix | Default | Use for | |--------|---------|---------| | `hide__` | `false` = visible | Features ON by default that can be turned off | | `show__` | `false` = hidden | Features OFF by default that can be turned on | **Never have both `show__foo` and `hide__foo` for the same concept.** - Visibility controls (codes, descriptions, POC, biography) β†’ default visible β†’ `hide__` - Opt-in features (access links, launcher, QR links) β†’ default hidden β†’ `show__` --- ## Canonical Remote Config Schema `PressMgmtRemoteCfg` β€” the authoritative TypeScript interface for `event.mod_pres_mgmt_json`: ```typescript interface PressMgmtRemoteCfg { // System lock_config: boolean; // true = force remoteβ†’local sync (prevent user overrides) // Labels (event-specific terminology overrides) label__person_external_id: string | null; // default: 'External ID' label__presenter_external_id: string | null; // default: 'External ID' label__session_poc_type: string | null; // e.g. 'champion', 'poc' label__session_poc_name: string | null; // e.g. 'Champion', 'Point of Contact' // Codes (visible by default β€” hide to suppress) hide__location_code: boolean; hide__presentation_code: boolean; hide__presenter_code: boolean; hide__session_code: boolean; // Session fields (visible by default) hide__session_description: boolean; hide__session_location: boolean; hide__session_msg: boolean; hide__session_poc: boolean; hide__session_poc_biography: boolean; hide__session_poc_profile_pic: boolean; // Presenter fields hide__presenter_biography: boolean; // Presentation fields hide__presentation_datetime: boolean; hide__presentation_description: boolean; // replaces show_content__presentation_description // Opt-in features (hidden by default β€” show to enable) show__copy_access_link: boolean; show__email_access_link: boolean; show__launcher_link: boolean; show__launcher_link_legacy: boolean; show__session_li_poc_field: boolean; // POC column in session list/table; hide__session_poc still wins // Requirements require__presenter_agree: boolean; require__session_agree: boolean; // Navigation/UI constraints limit__navigation: boolean; limit__options: boolean; // File upload config file_purpose_option_kv: Record | null; // Report visibility (key = report slug, value = true to hide) hide__report_kv: Record; } ``` ### Keys Removed vs. Current DB Records | Removed Key | Reason | |-------------|--------| | `file_purpose_option_li` | Duplicate of `file_purpose_option_kv` | | `HOLD__file_os_selection_option` | Dead/held feature | | `hide__copy_access_link` | Conflicts with `show__copy_access_link` β€” use `show__` | | `hide__email_access_link` | Conflicts with `show__email_access_link` β€” use `show__` | | `hide__launcher_link` | Conflicts with `show__launcher_link` β€” use `show__` | | `hide__launcher_link_legacy` | Conflicts with `show__launcher_link_legacy` β€” use `show__` | | `hide__report_li` | Superseded by `hide__report_kv` | | `show__navigation` | Ambiguous β€” covered by `limit__navigation` | | `label__session_poc_name_short` | Was a bug β€” never applied (overwritten immediately) | | `show_content__presentation_description` | Renamed to `hide__presentation_description` | --- ## New Svelte 5 Local Store **Do NOT touch `events_loc` or the paused Svelte 5 migration.** Instead, create a standalone store for pres_mgmt local config. **File:** `src/lib/stores/ae_events_stores__pres_mgmt.svelte.ts` ```typescript import { PersistedState } from 'runed'; import { pres_mgmt_loc_defaults } from './ae_events_stores__pres_mgmt_defaults'; export const pres_mgmt_loc = new PersistedState('ae_pres_mgmt_loc', pres_mgmt_loc_defaults); // Usage: pres_mgmt_loc.current.hide__session_code ``` - New localStorage key: `ae_pres_mgmt_loc` (separate from `ae_events_loc`) - Version gate: add `AE_PRES_MGMT_LOC_VERSION` to `store_versions.ts` - `sync_config__event_pres_mgmt()` writes to `pres_mgmt_loc.current` directly Consumer syntax change: ``` BEFORE: $events_loc.pres_mgmt.hide__session_code AFTER: pres_mgmt_loc.current.hide__session_code ``` --- ## Config UI Page **Route:** `/events/[event_id]/(pres_mgmt)/pres_mgmt/config/` **Access:** `$ae_loc.manager_access` only **Button visibility:** Edit mode only (`$ae_loc.edit_mode`) ### Page behavior - Loads `event.mod_pres_mgmt_json` fresh from API on page open - Displays grouped form sections (see below) - Save = load β†’ merge β†’ PATCH `/v3/crud/event/{event_id}` with `{ mod_pres_mgmt_json: updated }` - The existing settings form at `/events/[id]/settings` has its pres_mgmt section removed or replaced with a link ### Form sections (grouped) 1. **System** β€” `lock_config` 2. **Labels** β€” `label__*` fields (text inputs, nullable) 3. **Session Visibility** β€” `hide__session_*` toggles 4. **Presenter Visibility** β€” `hide__presenter_*` toggles 5. **Presentation Visibility** β€” `hide__presentation_*` toggles 6. **Code Visibility** β€” `hide__*_code` toggles 7. **Opt-in Features** β€” `show__*` toggles 8. **Requirements** β€” `require__presenter_agree`, `require__session_agree` 9. **Navigation Limits** β€” `limit__navigation`, `limit__options` 10. **File Purpose Config** β€” `file_purpose_option_kv` (JSON editor or structured form) 11. **Report Visibility** β€” `hide__report_kv` (key-value toggles) --- ## Migration Path Safe and backward compatible β€” old DB records fall through to `?? false` defaults. 1. No DB migration script needed β€” old keys are simply ignored by the updated sync function 2. Active events (BGH) get updated via the new UI after it's built 3. The `sync_config__event_pres_mgmt()` rewrite is the critical step β€” it must handle the canonical keys and clean defaults before the UI ships --- ## Implementation Steps - [x] **Step 1** β€” Define `PressMgmtRemoteCfg` TypeScript interface (new file or in `ae_events__event.ts`) - [x] **Step 2** β€” New `ae_events_stores__pres_mgmt.svelte.ts` with `PersistedState` ⚠️ **Missing:** version gate in `store_versions.ts` - [x] **Step 3** β€” Rewrite `sync_config__event_pres_mgmt()` in `ae_events__event.ts` to use canonical keys ⚠️ **Issue:** `show__launcher_link_legacy` hard-coded instead of synced from remote - [x] **Step 4** β€” Build config UI page at `(pres_mgmt)/pres_mgmt/config/+page.svelte` (manager_access + edit_mode gated) - [ ] **Step 5** β€” Strip `ae_comp__event_settings_pres_mgmt_form.svelte` from settings page (or replace with a link to new page) **BLOCKING** - [x] **Step 6** β€” Migrate all `$events_loc.pres_mgmt.*` references in pres_mgmt templates to `pres_mgmt_loc.current.*` - [ ] **Step 7** β€” Update BGH (and any other active events) via new UI (blocked on Step 5) - [ ] **Step 8** β€” `npx svelte-check` clean; commit ### Regression Fixes Needed (2026-06-12 Audit) - [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 `PressMgmtRemoteCfg` + Config UI (Session Field Visibility) + `sync_config__event_pres_mgmt()` lock-synced block; list/table column visibility is now `hide__session_poc || !show__session_li_poc_field` in `pres_mgmt/+page.svelte` and `locations/ae_comp__event_location_obj_li.svelte`. The local per-browser "Show/Hide POC Column" toggle buttons in `ae_comp__events_menu_opts.svelte` and `event_page_menu.svelte` were removed β€” the field is lock-synced from the per-event Config page now, same as the other session field visibility toggles. - [x] **Presenter QR matched to session QR pattern (2026-06-16)** β€” `show__session_qr` / `show__presenter_qr` are the admin-set **global default for everyone**, signed in or not. `show_content__*_qr` is a **trusted-staff-only local override**, used when the admin has NOT enabled QR globally. The session QR side already worked this way (fixed earlier today); presenter QR was still on the old buggy logic requiring `show_content__presenter_qr` to be true even when the admin had enabled it for everyone, which non-trusted users (presenters) have no way to set. Fixed `presenter_view.svelte` (generation effect + display block) to `show__presenter_qr || (trusted_access && show_content__presenter_qr)`. Also corrected two toggle-button visibility bugs found along the way: `ae_comp__events_menu_opts.svelte` was showing the QR toggle to all `authenticated_access` users (not just trusted) whenever the admin had enabled QR globally, even though the toggle had no effect for them; and `presenter_page_menu.svelte`'s QR toggle was gated to `administrator_access`, hiding it from plain Trusted onsite staff entirely. Both now use the canonical pattern from `session_page_menu.svelte`: `trusted_access && !show__*_qr`. - [x] **Missing `sync_config__event_pres_mgmt()` calls on deep-link entry pages (2026-06-16)** β€” root cause of the presenter QR bug above: a browser landing directly on the presenter page (e.g. via a presenter sign-in link) never synced remote config into `pres_mgmt_loc.current` at all, since only `pres_mgmt/+page.svelte` and `session/[session_id]/+page.svelte` called the sync function. Every "always synced" and "lock-synced" field (QR enables, POC visibility, code visibility, labels, etc.) silently stayed at hardcoded local defaults on that browser regardless of admin Config page settings. Added the same sync `$effect` (mirroring the existing comment/pattern in `session/[session_id]/+page.svelte`) to `presenter/[presenter_id]/+page.svelte`, `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) The `$events_loc.pres_mgmt` pattern appears across: - `ae_comp__event_session_obj_li.svelte` - `ae_comp__events_menu_opts.svelte` - `session/[session_id]/+page.svelte` - `session/[session_id]/session_view.svelte` - `session/[session_id]/session_page_menu.svelte` - `locations/locations_page_menu.svelte` - `reports/+page.svelte` - `pres_mgmt/+page.svelte` - (and likely others β€” run `grep -r 'events_loc.pres_mgmt' src/` to get full list) --- ## Notes - The `lock_config: true` default means most events will always sync from remote. This is intentional β€” it prevents presenter laptops from drifting into different configs. - `file_purpose_option_kv` may need a structured editor (not raw JSON) to be usable. Consider a simple key-value form row per purpose type for Phase 2. - QR link keys (`hide__presenter_qr_link`, `hide__session_qr_link`) appeared in LCI config but are not in the canonical schema above. Evaluate whether they're actively used before adding them back. - `limit__navigation` and `limit__options` are in the DB but not currently read by `sync_config__event_pres_mgmt()`. Confirm where they're consumed before adding to sync.