pres_mgmt: migrate to typed PersistedState store, canonical config schema

Replaces untyped $events_loc.pres_mgmt (svelte-persisted-store) with a
dedicated pres_mgmt_loc (runed PersistedState) backed by a fully typed
PresMgmtLocState interface and PressMgmtRemoteCfg for the server-side JSON.

Key changes:
- ae_events_stores__pres_mgmt_defaults.ts: canonical interfaces + defaults
  covering all hide__/show__ fields, labels, report prefs, query filters,
  and lock_config sync fields; qry_enabled uses 'not_enabled' (matches API)
- ae_events_stores__pres_mgmt.svelte.ts: new PersistedState store
- ae_events__event.ts: sync_config__event_pres_mgmt() rewired to write
  directly to pres_mgmt_loc.current; launcher link inversion preserved
- All 26+ pres_mgmt templates migrated from $events_loc.pres_mgmt.* to
  pres_mgmt_loc.current.*
- New config UI at (pres_mgmt)/pres_mgmt/config/ — manager + edit mode only
- Event settings page: removed embedded pres_mgmt form, links to config page
- event_page_menu: Config button visible only when manager_access + edit_mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-04-02 15:27:12 -04:00
parent 21f0fe69af
commit fd9e5f6dc0
31 changed files with 1426 additions and 661 deletions

View File

@@ -0,0 +1,18 @@
/**
* ae_events_stores__pres_mgmt.svelte.ts
*
* Svelte 5 PersistedState store for Presentation Management local config.
* Replaces the `events_loc.pres_mgmt` sub-object from the Svelte 4 persisted store.
*
* localStorage key: 'ae_pres_mgmt_loc'
* Version gate: AE_PRES_MGMT_LOC_VERSION in store_versions.ts
*
* Usage:
* import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte';
* pres_mgmt_loc.current.hide__session_code // read
* pres_mgmt_loc.current.hide__session_code = true // write
*/
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);

View File

@@ -1,19 +1,95 @@
/**
* ae_events_stores__pres_mgmt_defaults.ts
*
* Default state for the pres_mgmt (Presentation Management) section of ae_events_stores.ts.
* pres_mgmt_loc_defaults → events_loc.pres_mgmt (persisted to localStorage)
* pres_mgmt_sess_defaults → events_sess.pres_mgmt (in-memory, resets on page load)
* Type definitions and defaults for the Pres Mgmt stores.
*
* PressMgmtRemoteCfg → shape of event.mod_pres_mgmt_json (server-side admin config)
* PresMgmtLocState → pres_mgmt_loc (PersistedState, localStorage)
* PresMgmtSessState → events_sess.pres_mgmt (in-memory, resets on page load)
*
* NAMING CONVENTION (enforced here, must not mix):
* hide__* → feature is ON by default; true = turn it off (default: false = visible)
* show__* → feature is OFF by default; true = turn it on (default: false = hidden)
* Never use both hide__foo and show__foo for the same concept.
*/
// ---------------------------------------------------------------------------
// 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.
// ---------------------------------------------------------------------------
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'
label__session_poc_type: string | null; // e.g. 'champion', 'poc'
label__session_poc_name: string | null; // e.g. 'Champion', 'Point of Contact'
// Code visibility (visible by default)
hide__location_code: boolean;
hide__presentation_code: boolean;
hide__presenter_code: boolean;
hide__session_code: boolean;
// Session field visibility (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: boolean;
hide__session_poc_profile_pic: boolean;
// Presenter field visibility
hide__presenter_biography: boolean;
// Presentation field visibility
hide__presentation_datetime: boolean;
hide__presentation_description: boolean; // replaces show_content__presentation_description
// Opt-in features (hidden by default — set true to enable)
show__copy_access_link: boolean;
show__email_access_link: boolean;
show__launcher_link: boolean;
show__launcher_link_legacy: boolean;
// Requirements
require__presenter_agree: boolean;
require__session_agree: boolean;
// Navigation / UI constraints
limit__navigation: boolean;
limit__options: boolean;
// File upload options (null = use system default)
file_purpose_option_kv: Record<
string,
{ name: string; disabled?: boolean; hidden?: boolean }
> | null;
// Report section visibility (key = report slug, true = hide that report)
hide__report_kv: Record<string, boolean>;
}
// ---------------------------------------------------------------------------
// 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.
// ---------------------------------------------------------------------------
export interface PresMgmtLocState {
// --- System / lock state (mirrored from remote for display) ---
sync_local_config: boolean;
lock_config: boolean;
// --- Query / search preferences ---
datetime_format: string;
time_format: string;
time_hours: 12 | 24;
qry_enabled: string; // 'all' | 'disabled' | 'enabled'
qry_hidden: string; // 'all' | 'hidden' | 'not_hidden'
qry_enabled: 'all' | 'not_enabled' | 'enabled';
qry_hidden: 'all' | 'hidden' | 'not_hidden';
qry_limit__files: number;
qry_limit__presentations: number;
qry_limit__presenters: number;
@@ -26,11 +102,29 @@ export interface PresMgmtLocState {
qry_and__file_count: boolean;
save_search_text: boolean;
saved_search__session: string | null;
require__presenter_agree: boolean;
require__session_agree: boolean;
saved_search__session_location_name: string | null;
fulltext_search_qry_str: string | null; // persisted search text (when save_search_text=true)
location_name_qry_str: string | null; // persisted location filter text
refresh_interval: number; // auto-refresh interval in seconds (0 = disabled)
// --- Report display preferences (user-controlled, persisted) ---
rpt__session_no_files: boolean; // show "sessions with no files" report section
rpt__session_poc_agree: boolean; // show "session POC agreement" report section
rpt__session_no_bio: boolean; // show "sessions with no bio" report section
rpt__presenter_agree: boolean; // show "presenter agreement" report section
qry__file_purpose: string | null; // filter report files by purpose slug
qry__files_min_size: number | null; // filter report files by minimum size (bytes)
qry__session_sort: string; // sort order for session report
qry__presenter_sort: string; // sort order for presenter report
// --- Content / panel visibility (user preferences) ---
expand__menu_opts: boolean;
show_content__device_description: boolean;
show_content__event_view: string | null;
show_content__location_description: boolean;
show_content__location_devices_sessions: 'sessions' | 'devices' | null;
show_content__location_qr: boolean;
show_content__presentation_description: boolean;
show_content__presentation_description: boolean; // per-presentation expand toggle
show_content__presenter_page_help: boolean;
show_content__presenter_view: string | null;
show_content__presenter_qr: boolean;
@@ -43,34 +137,77 @@ export interface PresMgmtLocState {
show_content__session_search_room_name: boolean;
show_content__session_view: string | null;
show_content__session_qr: boolean;
hide__session_code: boolean;
hide__session_msg: boolean;
hide__session_poc: boolean;
hide__session_poc_biography: boolean;
hide__presenter_biography: boolean;
hide__session_li_location_field: boolean;
show__session_li_poc_field: boolean;
hide__launcher_link_legacy: boolean;
hide__launcher_link: boolean;
hide__location_link: boolean;
show_content__disabled_files: boolean;
show_content__hidden_files: boolean;
show_content__hidden_presentations: boolean;
show_content__hidden_presenters: boolean;
show_content__hidden_sessions: boolean;
// --- List/table column visibility (user preferences) ---
hide__device_code: boolean;
hide__locations_msg: boolean;
hide__session_li_location_field: boolean;
show__session_li_poc_field: boolean;
// Launcher/location links in session list
hide__launcher_link_legacy: boolean; // Flask/legacy launcher
hide__launcher_link: boolean; // SvelteKit launcher
hide__location_link: boolean;
show__direct_download: boolean;
// null = use server-side default.
// --- Menu visibility (null = use server-side default) ---
show_menu__presenter: boolean | null;
show_menu__session: boolean | null;
show_menu__session_search: boolean | null;
show_menu__event_reports: boolean | null;
show_report: string | null;
// --- Opt-out toggles ---
disable_submit__opt_out: boolean;
submit_status__opt_out: string | null;
// --- Device/location scratch space ---
device_kv: Record<string, any>;
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.
label__person_external_id: string;
label__presenter_external_id: string;
label__session_poc_type: string;
label__session_poc_name: string;
hide__location_code: boolean;
hide__presentation_code: boolean;
hide__presentation_datetime: boolean;
hide__presentation_description: boolean;
hide__presenter_code: boolean;
hide__presenter_biography: boolean;
hide__session_code: boolean;
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: boolean;
hide__session_poc_profile_pic: boolean;
show__copy_access_link: boolean;
show__email_access_link: boolean;
show__launcher_link: boolean;
show__launcher_link_legacy: boolean;
require__presenter_agree: boolean;
require__session_agree: boolean;
limit__navigation: boolean;
limit__options: boolean;
file_purpose_option_kv: Record<
string,
{ name: string; disabled?: boolean; hidden?: boolean }
> | null;
hide__report_kv: Record<string, boolean>;
}
// ---------------------------------------------------------------------------
// In-memory session state — resets on page load (not persisted)
// ---------------------------------------------------------------------------
export interface PresMgmtSessState {
presenter__updated_on: string | null;
session_updated_on: string | null;
@@ -114,40 +251,57 @@ export interface PresMgmtSessState {
tmp_val__filename_no_ext: string | null;
}
// Persisted pres_mgmt config — survives browser sessions.
export const pres_mgmt_loc_defaults: PresMgmtLocState = {
sync_local_config: false,
lock_config: true,
// ---------------------------------------------------------------------------
// Defaults
// ---------------------------------------------------------------------------
// 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
datetime_format: 'datetime_12_long',
time_format: 'time_12_short',
time_hours: 12, // 12 or 24
qry_enabled: 'enabled', // 'all' | 'disabled' | 'enabled'
qry_hidden: 'not_hidden', // 'all' | 'hidden' | 'not_hidden'
time_hours: 12,
qry_enabled: 'enabled',
qry_hidden: 'not_hidden',
qry_limit__files: 75,
qry_limit__presentations: 25,
qry_limit__presenters: 500,
qry_limit__sessions: 100,
qry_max: 500, // Maximum value the limit is allowed to be set to.
qry_max: 500,
qry__files_offset_seconds: null,
qry__files_sort: 'created_on',
// Search stabilization — version bump clears cache and re-runs (Standardized Pattern 2026-01-27).
search_version: 0,
qry__remote_first: false,
qry_and__file_count: true, // When true, only sessions with at least 1 file are returned.
qry_and__file_count: true,
save_search_text: true,
saved_search__session: null,
saved_search__session_location_name: null,
fulltext_search_qry_str: null,
location_name_qry_str: null,
refresh_interval: 0,
require__presenter_agree: false,
require__session_agree: false,
// Report display preferences
rpt__session_no_files: true,
rpt__session_poc_agree: false,
rpt__session_no_bio: true,
rpt__presenter_agree: true,
qry__file_purpose: null,
qry__files_min_size: null,
qry__session_sort: 'name',
qry__presenter_sort: 'name',
// Content / panel visibility
expand__menu_opts: false,
show_content__device_description: false,
show_content__location_description: false,
show_content__location_devices_sessions: null,
show_content__event_view: null,
show_content__location_qr: false,
show_content__presentation_description: false, // Global toggle — applies to all presentations.
show_content__presentation_description: false,
show_content__presenter_page_help: true,
show_content__presenter_view: null,
show_content__presenter_qr: false,
@@ -160,100 +314,108 @@ export const pres_mgmt_loc_defaults: PresMgmtLocState = {
show_content__session_search_room_name: false,
show_content__session_view: null,
show_content__session_qr: false,
hide__session_code: false, // Default visible; toggle in ae_comp__events_menu_opts to hide
hide__session_msg: true,
hide__session_poc: true,
hide__session_poc_biography: true,
hide__presenter_biography: true,
// List/table-specific visibility.
hide__session_li_location_field: false,
show__session_li_poc_field: false,
hide__launcher_link_legacy: true, // Flask version.
hide__launcher_link: true, // SvelteKit version.
hide__location_link: true,
show_content__disabled_files: false,
show_content__hidden_files: false,
show_content__hidden_presentations: false,
show_content__hidden_presenters: false,
show_content__hidden_sessions: false,
// List/table column visibility
hide__device_code: false,
hide__locations_msg: false,
hide__session_li_location_field: false,
show__session_li_poc_field: false,
hide__launcher_link_legacy: true,
hide__launcher_link: true,
hide__location_link: true,
show__direct_download: false,
// Menu visibility — null means "use server-side default".
// Use "hide" prefix for any new additions here.
// Menu visibility
show_menu__presenter: null,
show_menu__session: null,
show_menu__session_search: null,
show_menu__event_reports: null,
show_report: null,
// Opt-out
disable_submit__opt_out: true,
submit_status__opt_out: null,
// Scratch space
device_kv: {},
location_kv: {}
location_kv: {},
// Synced from remote (defaults apply until first event load)
label__person_external_id: 'External ID',
label__presenter_external_id: 'External ID',
label__session_poc_type: 'poc',
label__session_poc_name: 'Point of Contact',
hide__location_code: false,
hide__presentation_code: false,
hide__presentation_datetime: false,
hide__presentation_description: false,
hide__presenter_code: false,
hide__presenter_biography: true,
hide__session_code: false,
hide__session_description: false,
hide__session_location: false,
hide__session_msg: true,
hide__session_poc: true,
hide__session_poc_biography: true,
hide__session_poc_profile: false,
hide__session_poc_profile_pic: false,
show__copy_access_link: false,
show__email_access_link: false,
show__launcher_link: false,
show__launcher_link_legacy: false,
require__presenter_agree: false,
require__session_agree: false,
limit__navigation: false,
limit__options: false,
file_purpose_option_kv: null,
hide__report_kv: {}
};
// In-memory pres_mgmt state — resets on page load.
export const pres_mgmt_sess_defaults: PresMgmtSessState = {
presenter__updated_on: null,
session_updated_on: null,
location_name_qry_str: null,
fulltext_search_qry_str: null,
status_qry__search: null,
disable_submit__event_file_obj: true,
show_form__search: true,
show_form__search_results: true,
show_content__agree_text: false,
show_content__presenter_start: false,
show_content__presentation_description: false, // Per-presentation (keyed by event_presentation_id_random).
show_content__presentation_description: false,
show_report: null,
show_field_edit__filename: false, // For file rename.
show_field_edit__filename: false,
new_upload_list: null,
files_uploading_count: null,
qry_limit__files: 75,
qry_limit__presentations: 25,
qry_limit__presenters: 500,
qry_limit__sessions: 100,
show_fields__presentation: true,
show_fields__session: true,
show_modal__presenter_agree: false,
show__session_poc_profile: false,
show_modal__session_poc_agree: false,
show__edit_location: {},
show__edit_poc_person: {},
show__view_alert: {}, // Key-value: show alert by ID.
show__edit_alert_msg: {}, // Key-value: show edit alert msg by ID.
tmp__alert_msg: {}, // Key-value: temp alert msg content by ID.
session_qr_url: {}, // Key-value: session_id → URL string.
show__view_alert: {},
show__edit_alert_msg: {},
tmp__alert_msg: {},
session_qr_url: {},
status_rpt: {
recent_files: null,
presenters_agree: null,
presenters_biography: null
},
rpt__session_no_files: true,
rpt__session_poc_agree: false, // Default to false for new events.
rpt__session_poc_agree: false,
rpt__session_no_bio: true,
rpt__presenter_agree: true,
tmp_val__filename_no_ext: null // For file rename.
tmp_val__filename_no_ext: null
};

View File

@@ -33,6 +33,7 @@
export const AE_LOC_VERSION = 2; // Bumped 2026-03-30: force-clear stale site_cfg_json (novi_idaa_api_key missing bug)
export const AE_EVENTS_LOC_VERSION = 1;
export const AE_IDAA_LOC_VERSION = 1; // Added 2026-03-30: was missing, no wipe mechanism existed
export const AE_PRES_MGMT_LOC_VERSION = 1; // Added 2026-04-02: new standalone PersistedState store
// Version check side-effect: runs on import, before any persisted() call.
// Guard presence of `localStorage` and its functions for safety (SSR, Node flags).
@@ -44,6 +45,10 @@ 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_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.
}
function _check_and_wipe(key: string, expected_version: number): void {