fix(pres_mgmt): remove lock_config — sync is now fully unconditional

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. This was the actual root cause of the
"POC column / Hide POC sometimes works" reports.

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 in practice.

- Removed lock_config from PressMgmtRemoteCfg, PresMgmtLocState, the
  sync function (no more conditional block — every field syncs
  unconditionally now, 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, same as any
  other removed key — no migration needed
- Moved the now-fully-orphaned ae_comp__event_settings_pres_mgmt_form.svelte
  to ~/tmp/agents_trash (zero imports anywhere; the settings page already
  links to the canonical Config page) — completes Step 5 from the
  cleanup doc
- Marked PROJECT__AE_Events_PressMgmt_Config_Cleanup.md complete — all
  Implementation Steps and known regressions resolved

svelte-check: 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-16 13:56:27 -04:00
parent e5c141e765
commit af2236eea4
6 changed files with 119 additions and 280 deletions

View File

@@ -954,12 +954,20 @@ export async function process_ae_obj__event_props({
* Syncs the server-side event pres_mgmt config (mod_pres_mgmt_json) into the
* local PersistedState store (pres_mgmt_loc.current).
*
* Called reactively in pres_mgmt/+page.svelte whenever the event object changes.
* Called reactively whenever the event object changes — see the sync $effect
* in pres_mgmt/+page.svelte, session/[session_id]/+page.svelte,
* 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 needs the same block.
*
* Always-synced fields: labels, requirements, access links, file config.
* Lock-synced fields (only when lock_config=true): all visibility/hide__ toggles,
* launcher links, navigation limits. This prevents presenter laptops from
* drifting into different display configs across a conference.
* Every field in PressMgmtRemoteCfg is synced unconditionally, every call —
* no opt-out, no gate. (Removed 2026-06-16: a `lock_config` toggle used to make
* a subset of these fields conditional. In practice every event always had it
* set to true, and the conditional sync it created meant a field's local value
* depended on whatever lock_config happened to be during the LAST sync that
* touched that particular browser — not its current value. That's effectively
* undebuggable from the UI and was the root cause of several "sometimes works"
* bug reports. See PROJECT__AE_Events_PressMgmt_Config_Cleanup.md.)
*
* Canonical key convention (enforced here):
* hide__* → feature ON by default; true = turn off (default: false = visible)
@@ -981,8 +989,7 @@ export function sync_config__event_pres_mgmt({
const loc = pres_mgmt_loc.current;
// --- Always sync: labels, requirements, opt-in features, file config ---
// These are safe to always apply — they don't override local display preferences.
// --- Labels ---
loc.label__person_external_id =
pres_mgmt_cfg_remote?.label__person_external_id ?? 'External ID';
loc.label__presenter_external_id =
@@ -992,85 +999,77 @@ export function sync_config__event_pres_mgmt({
loc.label__session_poc_name =
pres_mgmt_cfg_remote?.label__session_poc_name ?? 'Point of Contact';
// --- Requirements ---
loc.require__presenter_agree =
pres_mgmt_cfg_remote?.require__presenter_agree ?? false;
loc.require__session_agree =
pres_mgmt_cfg_remote?.require__session_agree ?? false;
// --- Opt-in features ---
loc.show__copy_access_link =
pres_mgmt_cfg_remote?.show__copy_access_link ?? false;
loc.show__email_access_link =
pres_mgmt_cfg_remote?.show__email_access_link ?? false;
// QR code feature enable — always synced (admin opt-in, not a user preference)
loc.show__session_qr =
pres_mgmt_cfg_remote?.show__session_qr ?? false;
loc.show__presenter_qr =
pres_mgmt_cfg_remote?.show__presenter_qr ?? false;
// --- File / report config ---
loc.file_purpose_option_kv =
pres_mgmt_cfg_remote?.file_purpose_option_kv ?? null;
loc.hide__report_kv = pres_mgmt_cfg_remote?.hide__report_kv ?? {};
// --- Lock-synced: visibility, launcher, navigation, session/presenter fields ---
// When lock_config=true the remote config wins for ALL display flags, preventing
// local browser state from overriding the admin's event-level configuration.
if (pres_mgmt_cfg_remote?.lock_config) {
if (log_lvl) console.log(`sync_config__event_pres_mgmt: lock_config=true, forcing full sync`);
// --- Codes ---
loc.hide__location_code =
pres_mgmt_cfg_remote?.hide__location_code ?? false;
loc.hide__presentation_code =
pres_mgmt_cfg_remote?.hide__presentation_code ?? false;
loc.hide__presenter_code =
pres_mgmt_cfg_remote?.hide__presenter_code ?? false;
loc.hide__session_code =
pres_mgmt_cfg_remote?.hide__session_code ?? false;
// Codes
loc.hide__location_code =
pres_mgmt_cfg_remote?.hide__location_code ?? false;
loc.hide__presentation_code =
pres_mgmt_cfg_remote?.hide__presentation_code ?? false;
loc.hide__presenter_code =
pres_mgmt_cfg_remote?.hide__presenter_code ?? false;
loc.hide__session_code =
pres_mgmt_cfg_remote?.hide__session_code ?? false;
// --- Session fields ---
loc.hide__session_description =
pres_mgmt_cfg_remote?.hide__session_description ?? false;
loc.hide__session_location =
pres_mgmt_cfg_remote?.hide__session_location ?? false;
loc.hide__session_msg =
pres_mgmt_cfg_remote?.hide__session_msg ?? false;
loc.hide__session_poc =
pres_mgmt_cfg_remote?.hide__session_poc ?? false;
loc.show__session_li_poc_field =
pres_mgmt_cfg_remote?.show__session_li_poc_field ?? false;
loc.hide__session_poc_biography =
pres_mgmt_cfg_remote?.hide__session_poc_biography ?? false;
loc.hide__session_poc_profile =
pres_mgmt_cfg_remote?.hide__session_poc_profile ?? false;
loc.hide__session_poc_profile_pic =
pres_mgmt_cfg_remote?.hide__session_poc_profile_pic ?? false;
// Session fields
loc.hide__session_description =
pres_mgmt_cfg_remote?.hide__session_description ?? false;
loc.hide__session_location =
pres_mgmt_cfg_remote?.hide__session_location ?? false;
loc.hide__session_msg =
pres_mgmt_cfg_remote?.hide__session_msg ?? false;
loc.hide__session_poc =
pres_mgmt_cfg_remote?.hide__session_poc ?? false;
loc.show__session_li_poc_field =
pres_mgmt_cfg_remote?.show__session_li_poc_field ?? false;
loc.hide__session_poc_biography =
pres_mgmt_cfg_remote?.hide__session_poc_biography ?? false;
loc.hide__session_poc_profile =
pres_mgmt_cfg_remote?.hide__session_poc_profile ?? false;
loc.hide__session_poc_profile_pic =
pres_mgmt_cfg_remote?.hide__session_poc_profile_pic ?? false;
// --- Presenter fields ---
loc.hide__presenter_biography =
pres_mgmt_cfg_remote?.hide__presenter_biography ?? false;
// Presenter fields
loc.hide__presenter_biography =
pres_mgmt_cfg_remote?.hide__presenter_biography ?? false;
// --- Presentation fields ---
loc.hide__presentation_datetime =
pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false;
loc.hide__presentation_description =
pres_mgmt_cfg_remote?.hide__presentation_description ?? false;
// Presentation fields
loc.hide__presentation_datetime =
pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false;
loc.hide__presentation_description =
pres_mgmt_cfg_remote?.hide__presentation_description ?? false;
// --- 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.
loc.show__launcher_link =
pres_mgmt_cfg_remote?.show__launcher_link ?? false;
// 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 =
pres_mgmt_cfg_remote?.limit__navigation ?? false;
}
// --- Navigation / UI constraints ---
loc.limit__navigation =
pres_mgmt_cfg_remote?.limit__navigation ?? false;
}
/**

View File

@@ -360,10 +360,9 @@ function handle_clear_access() {
<!-- 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).
confirmed via full-repo grep. It also confusingly shared a name with the Pres
Mgmt Config page's own "Lock Config" checkbox, which has since been removed
entirely too — sync_config__event_pres_mgmt() now syncs unconditionally.
See PROJECT__AE_Events_PressMgmt_Config_Cleanup.md. -->
<!-- {#if $ae_loc.edit_mode}

View File

@@ -15,13 +15,14 @@
// ---------------------------------------------------------------------------
// Remote config — shape of event.mod_pres_mgmt_json
// This is the admin-controlled, server-side config for an event.
// lock_config: true forces every field below to sync into local state on load.
// This is the admin-controlled, server-side config for an event. Every field
// here is synced into every browser unconditionally on event load — there is
// no opt-out. (Removed 2026-06-16: a `lock_config` toggle used to gate this;
// in practice every event always had it set to true and the conditional
// sync it created was an undebuggable source of stale local state — see
// PROJECT__AE_Events_PressMgmt_Config_Cleanup.md.)
// ---------------------------------------------------------------------------
export interface PressMgmtRemoteCfg {
// System
lock_config: boolean; // true = force all fields below into local state
// Labels — event-specific terminology overrides
label__person_external_id: string | null; // default: 'External ID'
label__presenter_external_id: string | null; // default: 'External ID'
@@ -83,7 +84,7 @@ export interface PressMgmtRemoteCfg {
// ---------------------------------------------------------------------------
// Local state — persisted to localStorage via PersistedState ('ae_pres_mgmt_loc')
// Contains both user preferences AND fields synced from PressMgmtRemoteCfg.
// Fields synced from remote are overwritten on every event load when lock_config=true.
// Fields synced from remote are overwritten unconditionally on every event load.
// ---------------------------------------------------------------------------
export interface PresMgmtLocState {
// --- Query / search preferences ---
@@ -152,8 +153,8 @@ export interface PresMgmtLocState {
hide__device_code: boolean;
hide__locations_msg: boolean;
hide__session_li_location_field: boolean;
// Also mirrors PressMgmtRemoteCfg.show__session_li_poc_field — lock-synced
// (overwritten when lock_config=true), see sync_config__event_pres_mgmt().
// Also mirrors PressMgmtRemoteCfg.show__session_li_poc_field — synced
// unconditionally, see sync_config__event_pres_mgmt().
show__session_li_poc_field: boolean;
// Launcher/location links in session list
hide__launcher_link: boolean; // SvelteKit launcher
@@ -176,8 +177,8 @@ export interface PresMgmtLocState {
location_kv: Record<string, any>;
// --- Synced from PressMgmtRemoteCfg (overwritten by sync_config__event_pres_mgmt) ---
// These mirror PressMgmtRemoteCfg fields and are written during event load.
// Do not set these manually — they will be overridden when lock_config=true.
// These mirror PressMgmtRemoteCfg fields and are written unconditionally on every
// event load. Do not set these manually — they will always be overwritten.
label__person_external_id: string;
label__presenter_external_id: string;
label__session_poc_type: string;

View File

@@ -48,7 +48,6 @@ let lq__event_obj = $derived(
// Draft state — initialized from the live event config
// ---------------------------------------------------------------------------
const cfg_defaults: PressMgmtRemoteCfg = {
lock_config: true,
label__person_external_id: null,
label__presenter_external_id: null,
label__session_poc_type: null,
@@ -181,7 +180,6 @@ async function save() {
// Section collapse state
let sections: Record<string, boolean> = $state({
system: true,
labels: true,
codes: true,
session: true,
@@ -244,41 +242,13 @@ function toggle(key: string) {
<p class="text-surface-500 text-sm">
Changes here update <code>event.mod_pres_mgmt_json</code> and take effect on
the next event load. When <strong>Lock Config</strong> is on, all display flags
are pushed to every browser — overriding local user preferences.
the next event load, applying to every browser.
</p>
{#if !draft_initialized}
<p class="text-surface-400 italic">Loading event config...</p>
{:else}
<!-- ================================================================ -->
<!-- SYSTEM -->
<!-- ================================================================ -->
<section class="border-surface-200-800 rounded-xl border">
<button
type="button"
class="flex w-full items-center justify-between px-4 py-3 text-left font-semibold"
onclick={() => toggle('system')}>
<span>System</span>
{#if sections.system}<ChevronUp size="1em" />{:else}<ChevronDown size="1em" />{/if}
</button>
{#if sections.system}
<div class="border-surface-200-800 space-y-3 border-t px-4 py-3">
<label class="flex items-start gap-3">
<input type="checkbox" class="checkbox mt-0.5" bind:checked={draft.lock_config} />
<span>
<span class="font-semibold">Lock Config</span>
<span class="text-surface-500 ml-2 text-sm">
Forces all display flags below to sync into every browser on event load,
overriding local user preferences. Recommended: <strong>on</strong>.
</span>
</span>
</label>
</div>
{/if}
</section>
<!-- ================================================================ -->
<!-- LABELS -->
<!-- ================================================================ -->

View File

@@ -1,149 +0,0 @@
<script lang="ts">
import type { key_val } from '$lib/stores/ae_stores';
interface Props {
mod_pres_mgmt_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
let { mod_pres_mgmt_json = $bindable({}), onsave }: Props = $props();
function save() {
if (onsave && mod_pres_mgmt_json) onsave(mod_pres_mgmt_json);
}
</script>
<div class="space-y-4">
{#if mod_pres_mgmt_json}
<div class="grid grid-cols-2 gap-4">
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.lock_config} />
<span>Lock Config</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.hide__location_code} />
<span>Hide Location Code</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={
mod_pres_mgmt_json.hide__presentation_code
} />
<span>Hide Presentation Code</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={
mod_pres_mgmt_json.hide__presenter_code
} />
<span>Hide Presenter Code</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.hide__session_code} />
<span>Hide Session Code</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.limit__navigation} />
<span>Limit Navigation</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.limit__options} />
<span>Limit Options</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={
mod_pres_mgmt_json.require__presenter_agree
} />
<span>Require Presenter Agreement</span>
</label>
</div>
<div>
<label class="label">
<input
type="checkbox"
class="checkbox"
bind:checked={
mod_pres_mgmt_json.require__session_agree
} />
<span>Require Session Agreement</span>
</label>
</div>
</div>
<div class="space-y-2">
<div>
<label class="label">
<span>Label for Person External ID</span>
<input
type="text"
class="input"
bind:value={
mod_pres_mgmt_json.label__person_external_id
} />
</label>
</div>
<div>
<label class="label">
<span>Label for Session POC Type</span>
<input
type="text"
class="input"
bind:value={
mod_pres_mgmt_json.label__session_poc_type
} />
</label>
</div>
<div>
<label class="label">
<span>Label for Session POC Name</span>
<input
type="text"
class="input"
bind:value={
mod_pres_mgmt_json.label__session_poc_name
} />
</label>
</div>
</div>
{/if}
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button>
</div>