Same bug pattern as the earlier POC column fix. The Session Search
results table's Location column prop only ever read the local-only,
never-synced hide__session_li_location_field — never the admin-synced
hide__session_location (Config page > Session Field Visibility > Hide
Location). Column always showed regardless of that setting or
permission level.
Fixed in pres_mgmt/+page.svelte:
hide__session_location || hide__session_li_location_field
The other two usages of this component already hardcode
hide__session_location={true} correctly (you're already on that
location's own page, showing its name again would be redundant).
Logged in PROJECT__AE_Events_PressMgmt_Config_Cleanup.md with a note to
audit other hide__* fields for the same gap if more reports come in.
svelte-check: 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
367 lines
20 KiB
Markdown
367 lines
20 KiB
Markdown
# Project: Pres Mgmt Config Cleanup & Config UI
|
|
|
|
**Status:** 🟢 Complete — all Implementation Steps and known regressions resolved
|
|
**Priority:** Low (maintenance/monitoring only — re-open if new sync inconsistencies surface)
|
|
**Created:** 2026-04-02
|
|
**Last Updated:** 2026-06-16 (`lock_config` removed; sync is now fully unconditional)
|
|
**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 {
|
|
// No system/lock_config field — every field below syncs unconditionally,
|
|
// to every browser, on every event load. Removed 2026-06-16; see note below.
|
|
|
|
// 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<string, {
|
|
name: string;
|
|
disabled?: boolean;
|
|
hidden?: boolean;
|
|
}> | null;
|
|
|
|
// Report visibility (key = report slug, value = true to hide)
|
|
hide__report_kv: Record<string, boolean>;
|
|
}
|
|
```
|
|
|
|
### 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)
|
|
|
|
No System section — there is no `lock_config` (removed 2026-06-16).
|
|
|
|
1. **Labels** — `label__*` fields (text inputs, nullable)
|
|
2. **Session Visibility** — `hide__session_*` toggles
|
|
3. **Presenter Visibility** — `hide__presenter_*` toggles
|
|
4. **Presentation Visibility** — `hide__presentation_*` toggles
|
|
5. **Code Visibility** — `hide__*_code` toggles
|
|
6. **Opt-in Features** — `show__*` toggles
|
|
7. **Requirements** — `require__presenter_agree`, `require__session_agree`
|
|
8. **Navigation Limits** — `limit__navigation` (`limit__options` removed — YAGNI)
|
|
9. **File Purpose Config** — `file_purpose_option_kv` (JSON editor or structured form)
|
|
10. **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`, version gate wired (2026-06-16)
|
|
- [x] **Step 3** — Rewrite `sync_config__event_pres_mgmt()` in `ae_events__event.ts` to use canonical keys; syncs unconditionally, no `lock_config` gate (2026-06-16)
|
|
- [x] **Step 4** — Build config UI page at `(pres_mgmt)/pres_mgmt/config/+page.svelte` (manager_access + edit_mode gated)
|
|
- [x] **Step 5** — `ae_comp__event_settings_pres_mgmt_form.svelte` moved to trash (2026-06-16) — it was already fully orphaned (zero imports anywhere); the settings page already links to the canonical Config page with a raw-JSON fallback
|
|
- [x] **Step 6** — Migrate all `$events_loc.pres_mgmt.*` references in pres_mgmt templates to `pres_mgmt_loc.current.*`
|
|
- [x] **Step 7** — Active events' `lock_config` keys are now just inert orphan data in the DB, ignored by the sync function — no migration needed, nothing to update
|
|
- [x] **Step 8** — `npx svelte-check` clean
|
|
|
|
### 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`.
|
|
- [x] **`lock_config` removed from `PressMgmtRemoteCfg` entirely (2026-06-16)** — the real
|
|
one this time (the item above was a different, dead local-only mirror of the same name).
|
|
This was the actual root cause of the "POC column / Hide POC sometimes works" reports.
|
|
`lock_config` made roughly half of `sync_config__event_pres_mgmt()`'s fields conditional:
|
|
they only updated in a given browser if `lock_config` happened to be `true` at the exact
|
|
moment that browser last synced. A field's local value therefore depended on the *history*
|
|
of saves, not the current setting — undebuggable from the UI, and easy to corrupt by
|
|
toggling Lock Config off even briefly during testing. Checked the DB: every event, old and
|
|
new, already had `lock_config: true` — the "unlocked, per-browser preference" use case it
|
|
was built for has never actually been used. Removed it from `PressMgmtRemoteCfg`,
|
|
`PresMgmtLocState`, the sync function (no more conditional block — every field syncs
|
|
unconditionally, same as labels/Require Agreements always did), and the Config page UI
|
|
(no more System section). Old DB records with a `lock_config` key are simply ignored now,
|
|
same as any other removed key. Moved the now-fully-orphaned
|
|
`ae_comp__event_settings_pres_mgmt_form.svelte` to trash in the same pass (Step 5).
|
|
- [x] **Location column ignored the admin's "Hide Location" setting (2026-06-16)** — same
|
|
bug pattern as the POC column fix earlier, just missed for Location. The Session Search
|
|
results table's Location column prop only ever read the local-only, never-synced
|
|
`hide__session_li_location_field` — it never looked at the admin-synced
|
|
`hide__session_location` (Config page → Session Field Visibility → Hide Location) at all.
|
|
So the column always showed regardless of that setting or the user's permission level,
|
|
exactly as reported. Fixed in `pres_mgmt/+page.svelte`:
|
|
`hide__session_location || hide__session_li_location_field`. The two other usages of this
|
|
component (`locations/ae_comp__event_location_obj_li.svelte`,
|
|
`location/[event_location_id]/+page.svelte`) already hardcode `hide__session_location={true}`
|
|
— correct, since you're already on that location's own page. Worth auditing the other
|
|
per-event hide__* fields for the same "admin field exists but the list/table prop
|
|
computation never reads it" gap if more reports come in.
|
|
|
|
### 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
|
|
|
|
- All pres_mgmt display config now always syncs from remote, unconditionally, to every
|
|
browser, on every event load (no `lock_config` toggle — removed 2026-06-16). This is
|
|
intentional — it prevents presenter laptops from drifting into different configs, which
|
|
is exactly what `lock_config: true` was meant to guarantee, minus the conditional that
|
|
made it fragile.
|
|
- `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.
|