feat(pres_mgmt): self-documenting Config page + split POC into its own section

Two things prompted by direct feedback after testing:

1. Every input on the Config page now has a title tooltip explaining what
   it actually controls, where, and any interaction with other settings
   (e.g. clarifying Hide Location affects both the session detail page AND
   the Location column in Session Search results — not obvious from the
   label alone).

2. Split "Session Field Visibility" into "Session Display" (just
   description/location/message) and a new dedicated "POC Settings"
   section. Previously hide__session_poc ("Hide POC", checked = remove)
   sat directly next to show__session_li_poc_field ("Show POC Column",
   checked = add) in the same flat list — same checked state meaning
   opposite things for adjacent fields, confusing even though each field's
   own hide__/show__ naming is internally consistent with the project
   convention. Hide POC is now rendered as a visually distinct master
   switch (bold, its own row) with the three dependent settings indented
   under a left border below it and auto-disabled (with reduced opacity)
   whenever Hide POC is checked, so the "no effect once hidden" dependency
   is visible in the UI, not just documented in a footnote.

No field semantics changed — same PressMgmtRemoteCfg, same sync function,
same save path. Pure presentation/documentation pass.

Logged the rationale 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:
Scott Idem
2026-06-16 14:32:56 -04:00
parent cbc94babe0
commit ba2558fbf7
2 changed files with 112 additions and 42 deletions

View File

@@ -179,15 +179,26 @@ AFTER: pres_mgmt_loc.current.hide__session_code
No System section — there is no `lock_config` (removed 2026-06-16). No System section — there is no `lock_config` (removed 2026-06-16).
1. **Labels**`label__*` fields (text inputs, nullable) 1. **Labels**`label__*` fields (text inputs, nullable)
2. **Session Visibility**`hide__session_*` toggles 2. **Code Visibility**`hide__*_code` toggles
3. **Presenter Visibility**`hide__presenter_*` toggles 3. **Session Display**`hide__session_description/location/msg` (just the simple ones —
4. **Presentation Visibility**`hide__presentation_*` toggles POC split out below 2026-06-16 for clarity)
5. **Code Visibility**`hide__*_code` toggles 4. **POC Settings** (Point of Contact) — `hide__session_poc` rendered as a visually distinct
6. **Opt-in Features**`show__*` toggles master switch, with `show__session_li_poc_field` / `hide__session_poc_biography` /
7. **Requirements**`require__presenter_agree`, `require__session_agree` `hide__session_poc_profile` / `hide__session_poc_profile_pic` indented and
8. **Navigation Limits**`limit__navigation` (`limit__options` removed — YAGNI) auto-`disabled` underneath it when the master switch is on. Was previously flat in
9. **File Purpose Config**`file_purpose_option_kv` (JSON editor or structured form) "Session Field Visibility" with `hide__session_poc` and `show__session_li_poc_field`
10. **Report Visibility**`hide__report_kv` (key-value toggles) sitting adjacent with opposite checked-state polarity ("Hide POC" checked = remove,
"Show POC Column" checked = add) — confusing, per direct user feedback. Splitting it
into its own section with explicit master/sub-setting hierarchy fixed it without
touching the underlying field semantics (still `hide__`/`show__` per the naming
convention — only the *presentation* changed).
5. **Presenter Visibility**`hide__presenter_*` toggles
6. **Presentation Visibility**`hide__presentation_*` toggles
7. **Opt-in Features**`show__*` toggles
8. **Requirements**`require__presenter_agree`, `require__session_agree`
9. **Navigation Limits**`limit__navigation` (`limit__options` removed — YAGNI)
10. **File Purpose Config**`file_purpose_option_kv` (JSON editor or structured form)
11. **Report Visibility**`hide__report_kv` (key-value toggles)
--- ---

View File

@@ -183,6 +183,7 @@ let sections: Record<string, boolean> = $state({
labels: true, labels: true,
codes: true, codes: true,
session: true, session: true,
poc: true,
presenter: true, presenter: true,
presentation: true, presentation: true,
opt_in: true, opt_in: true,
@@ -268,6 +269,7 @@ function toggle(key: string) {
type="text" type="text"
class="input" class="input"
placeholder="External ID" placeholder="External ID"
title="Overrides the label shown for a Person's external ID field across the app. Leave blank to use the default 'External ID'."
bind:value={draft.label__person_external_id} /> bind:value={draft.label__person_external_id} />
</label> </label>
<label class="flex flex-col gap-1"> <label class="flex flex-col gap-1">
@@ -276,6 +278,7 @@ function toggle(key: string) {
type="text" type="text"
class="input" class="input"
placeholder="External ID" placeholder="External ID"
title="Overrides the label shown for a Presenter's external ID field. Leave blank to use the default 'External ID'."
bind:value={draft.label__presenter_external_id} /> bind:value={draft.label__presenter_external_id} />
</label> </label>
<label class="flex flex-col gap-1"> <label class="flex flex-col gap-1">
@@ -284,6 +287,7 @@ function toggle(key: string) {
type="text" type="text"
class="input" class="input"
placeholder="poc" placeholder="poc"
title="A short slug for what to call a session's Point of Contact (e.g. 'champion', 'chair'). Used internally — Session POC Name below is what's actually displayed to users."
bind:value={draft.label__session_poc_type} /> bind:value={draft.label__session_poc_type} />
<span class="text-surface-500 text-xs">e.g. "champion", "poc", "chair"</span> <span class="text-surface-500 text-xs">e.g. "champion", "poc", "chair"</span>
</label> </label>
@@ -293,6 +297,7 @@ function toggle(key: string) {
type="text" type="text"
class="input" class="input"
placeholder="Point of Contact" placeholder="Point of Contact"
title="The display label shown to users for a session's Point of Contact (e.g. 'Champion', 'Host'). Leave blank to use the default 'Point of Contact'."
bind:value={draft.label__session_poc_name} /> bind:value={draft.label__session_poc_name} />
<span class="text-surface-500 text-xs">e.g. "Champion", "Point of Contact"</span> <span class="text-surface-500 text-xs">e.g. "Champion", "Point of Contact"</span>
</label> </label>
@@ -314,12 +319,12 @@ function toggle(key: string) {
{#if sections.codes} {#if sections.codes}
<div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3">
{#each [ {#each [
{ field: 'hide__session_code' as const, label: 'Hide Session Code' }, { field: 'hide__session_code' as const, label: 'Hide Session Code', title: "Hides the session's short code wherever sessions are listed or viewed." },
{ field: 'hide__location_code' as const, label: 'Hide Location Code' }, { field: 'hide__location_code' as const, label: 'Hide Location Code', title: "Hides the location's short code wherever locations are listed or viewed." },
{ field: 'hide__presenter_code' as const, label: 'Hide Presenter Code' }, { field: 'hide__presenter_code' as const, label: 'Hide Presenter Code', title: "Hides the presenter's short code. Not currently shown anywhere in the app — has no visible effect yet." },
{ field: 'hide__presentation_code' as const, label: 'Hide Presentation Code' } { field: 'hide__presentation_code' as const, label: 'Hide Presentation Code', title: "Hides the presentation's short code wherever presentations are listed or viewed." }
] as item (item.field)} ] as item (item.field)}
<label class="flex items-center gap-2"> <label class="flex items-center gap-2" title={item.title}>
<input type="checkbox" class="checkbox" bind:checked={draft[item.field]} /> <input type="checkbox" class="checkbox" bind:checked={draft[item.field]} />
<span class="text-sm">{item.label}</span> <span class="text-sm">{item.label}</span>
</label> </label>
@@ -329,38 +334,77 @@ function toggle(key: string) {
</section> </section>
<!-- ================================================================ --> <!-- ================================================================ -->
<!-- SESSION FIELDS --> <!-- SESSION DISPLAY -->
<!-- ================================================================ --> <!-- ================================================================ -->
<section class="border-surface-200-800 rounded-xl border"> <section class="border-surface-200-800 rounded-xl border">
<button <button
type="button" type="button"
class="flex w-full items-center justify-between px-4 py-3 text-left font-semibold" class="flex w-full items-center justify-between px-4 py-3 text-left font-semibold"
onclick={() => toggle('session')}> onclick={() => toggle('session')}>
<span>Session Field Visibility</span> <span>Session Display</span>
{#if sections.session}<ChevronUp size="1em" />{:else}<ChevronDown size="1em" />{/if} {#if sections.session}<ChevronUp size="1em" />{:else}<ChevronDown size="1em" />{/if}
</button> </button>
{#if sections.session} {#if sections.session}
<div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3">
{#each [ {#each [
{ field: 'hide__session_description' as const, label: 'Hide Description' }, { field: 'hide__session_description' as const, label: 'Hide Description', title: "Hides the session's description text on the session detail page." },
{ field: 'hide__session_location' as const, label: 'Hide Location' }, { field: 'hide__session_location' as const, label: 'Hide Location', title: 'Hides the location name/links on the session detail page, AND hides the Location column in session list/table views (including Session Search results).' },
{ field: 'hide__session_msg' as const, label: 'Hide Message' }, { field: 'hide__session_msg' as const, label: 'Hide Message', title: "Hides the session's custom message/alert text." }
{ field: 'hide__session_poc' as const, label: 'Hide POC' },
{ field: 'show__session_li_poc_field' as const, label: 'Show POC Column in Lists/Tables' },
{ field: 'hide__session_poc_biography' as const, label: 'Hide POC Biography' },
{ field: 'hide__session_poc_profile' as const, label: 'Hide POC Profile' },
{ field: 'hide__session_poc_profile_pic' as const, label: 'Hide POC Profile Pic' }
] as item (item.field)} ] as item (item.field)}
<label class="flex items-center gap-2"> <label class="flex items-center gap-2" title={item.title}>
<input type="checkbox" class="checkbox" bind:checked={draft[item.field]} /> <input type="checkbox" class="checkbox" bind:checked={draft[item.field]} />
<span class="text-sm">{item.label}</span> <span class="text-sm">{item.label}</span>
</label> </label>
{/each} {/each}
</div> </div>
<p class="text-surface-500 border-surface-200-800 border-t px-4 py-2 text-xs"> {/if}
<strong>Hide POC</strong> is the master switch — when on, POC is hidden everywhere </section>
(session detail and list/table column) regardless of the column setting below it.
</p> <!-- ================================================================ -->
<!-- POC (POINT OF CONTACT) SETTINGS -->
<!-- ================================================================ -->
<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('poc')}>
<span>POC Settings <span class="text-surface-500 text-xs font-normal">(Point of Contact)</span></span>
{#if sections.poc}<ChevronUp size="1em" />{:else}<ChevronDown size="1em" />{/if}
</button>
{#if sections.poc}
<div class="border-surface-200-800 border-t px-4 py-3">
<label
class="flex items-start gap-2"
title="Master switch. When on, the POC is hidden everywhere — the session detail page AND the column in session list/table views (including the setting below) — no exceptions.">
<input type="checkbox" class="checkbox mt-0.5" bind:checked={draft.hide__session_poc} />
<span>
<span class="text-sm font-semibold">Hide POC</span>
<span class="text-surface-500 ml-1 text-xs">— master switch, overrides everything below</span>
</span>
</label>
<!-- Sub-settings only matter while Hide POC (above) is off — indented and
disabled when it's on to make that dependency visible, not just documented. -->
<div
class="border-surface-200-800 mt-3 ml-6 grid grid-cols-2 gap-3 border-l pl-4"
class:opacity-40={draft.hide__session_poc}>
{#each [
{ field: 'show__session_li_poc_field' as const, label: 'Show POC Column in Lists/Tables', title: "Adds a POC column to session list/table views (e.g. Session Search results). Off by default — check to opt in. No effect if 'Hide POC' above is checked." },
{ field: 'hide__session_poc_biography' as const, label: 'Hide POC Biography', title: "Hides the POC's biography text wherever it would otherwise be shown. No effect if 'Hide POC' above is checked." },
{ field: 'hide__session_poc_profile' as const, label: 'Hide POC Profile', title: "Hides the link/button to view the POC's full profile. No effect if 'Hide POC' above is checked." },
{ field: 'hide__session_poc_profile_pic' as const, label: 'Hide POC Profile Pic', title: "Hides the POC's profile picture. No effect if 'Hide POC' above is checked." }
] as item (item.field)}
<label class="flex items-center gap-2" title={item.title}>
<input
type="checkbox"
class="checkbox"
disabled={draft.hide__session_poc}
bind:checked={draft[item.field]} />
<span class="text-sm">{item.label}</span>
</label>
{/each}
</div>
</div>
{/if} {/if}
</section> </section>
@@ -377,7 +421,9 @@ function toggle(key: string) {
</button> </button>
{#if sections.presenter} {#if sections.presenter}
<div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3">
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Hides the presenter's biography text on the presenter detail page.">
<input type="checkbox" class="checkbox" bind:checked={draft.hide__presenter_biography} /> <input type="checkbox" class="checkbox" bind:checked={draft.hide__presenter_biography} />
<span class="text-sm">Hide Biography</span> <span class="text-sm">Hide Biography</span>
</label> </label>
@@ -398,11 +444,15 @@ function toggle(key: string) {
</button> </button>
{#if sections.presentation} {#if sections.presentation}
<div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3">
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Hides the presentation's date and time.">
<input type="checkbox" class="checkbox" bind:checked={draft.hide__presentation_datetime} /> <input type="checkbox" class="checkbox" bind:checked={draft.hide__presentation_datetime} />
<span class="text-sm">Hide Date/Time</span> <span class="text-sm">Hide Date/Time</span>
</label> </label>
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Hides the presentation's description text.">
<input type="checkbox" class="checkbox" bind:checked={draft.hide__presentation_description} /> <input type="checkbox" class="checkbox" bind:checked={draft.hide__presentation_description} />
<span class="text-sm">Hide Description</span> <span class="text-sm">Hide Description</span>
</label> </label>
@@ -424,13 +474,13 @@ function toggle(key: string) {
{#if sections.opt_in} {#if sections.opt_in}
<div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3">
{#each [ {#each [
{ field: 'show__copy_access_link' as const, label: 'Copy Access Link' }, { field: 'show__copy_access_link' as const, label: 'Copy Access Link', title: "Shows a 'Copy Access Link' button so Trusted staff can quickly copy a presenter/POC's sign-in link." },
{ field: 'show__email_access_link' as const, label: 'Email Access Link' }, { field: 'show__email_access_link' as const, label: 'Email Access Link', title: "Shows an 'Email Access Link' button so Trusted staff can email a presenter/POC their sign-in link." },
{ field: 'show__launcher_link' as const, label: 'Launcher Link' }, { field: 'show__launcher_link' as const, label: 'Launcher Link', title: 'Shows the Launcher link/icon to everyone in session lists. Trusted staff always see it regardless of this setting.' },
{ field: 'show__session_qr' as const, label: 'Session QR Code' }, { field: 'show__session_qr' as const, label: 'Session QR Code', title: 'Shows a QR code on the session detail page to everyone, signed in or not. Independent of this, Trusted staff can locally turn it on/off for just their own browser via the session Options menu.' },
{ field: 'show__presenter_qr' as const, label: 'Presenter QR Code' } { field: 'show__presenter_qr' as const, label: 'Presenter QR Code', title: 'Shows a QR code on the presenter detail page to everyone, signed in or not. Independent of this, Trusted staff can locally turn it on/off for just their own browser via the presenter Options menu.' }
] as item (item.field)} ] as item (item.field)}
<label class="flex items-center gap-2"> <label class="flex items-center gap-2" title={item.title}>
<input type="checkbox" class="checkbox" bind:checked={draft[item.field]} /> <input type="checkbox" class="checkbox" bind:checked={draft[item.field]} />
<span class="text-sm">{item.label}</span> <span class="text-sm">{item.label}</span>
</label> </label>
@@ -452,11 +502,15 @@ function toggle(key: string) {
</button> </button>
{#if sections.requirements} {#if sections.requirements}
<div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-2 gap-3 border-t px-4 py-3">
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Requires each presenter to agree to terms before participating; adds an Agreed/Not Agreed action to the presenter menu.">
<input type="checkbox" class="checkbox" bind:checked={draft.require__presenter_agree} /> <input type="checkbox" class="checkbox" bind:checked={draft.require__presenter_agree} />
<span class="text-sm">Require Presenter Agreement</span> <span class="text-sm">Require Presenter Agreement</span>
</label> </label>
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Requires the session POC to agree to terms before participating; adds an Agreed/Not Agreed action to the session menu.">
<input type="checkbox" class="checkbox" bind:checked={draft.require__session_agree} /> <input type="checkbox" class="checkbox" bind:checked={draft.require__session_agree} />
<span class="text-sm">Require Session Agreement</span> <span class="text-sm">Require Session Agreement</span>
</label> </label>
@@ -478,7 +532,9 @@ function toggle(key: string) {
{#if sections.navigation} {#if sections.navigation}
<div class="border-surface-200-800 grid grid-cols-1 gap-3 border-t px-4 py-3"> <div class="border-surface-200-800 grid grid-cols-1 gap-3 border-t px-4 py-3">
<p class="text-surface-500 text-xs">Hides the Session Search link for non-staff users, keeping presenters within their assigned session.</p> <p class="text-surface-500 text-xs">Hides the Session Search link for non-staff users, keeping presenters within their assigned session.</p>
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Hides the Session Search nav link for non-staff users (Trusted access and above always see it).">
<input type="checkbox" class="checkbox" bind:checked={draft.limit__navigation} /> <input type="checkbox" class="checkbox" bind:checked={draft.limit__navigation} />
<span class="text-sm">Limit Navigation (hide Session Search for non-staff)</span> <span class="text-sm">Limit Navigation (hide Session Search for non-staff)</span>
</label> </label>
@@ -508,6 +564,7 @@ function toggle(key: string) {
class="input font-mono text-xs" class="input font-mono text-xs"
rows={10} rows={10}
placeholder='&#123; "final": &#123; "name": "3. Final", "disabled": false &#125; &#125;' placeholder='&#123; "final": &#123; "name": "3. Final", "disabled": false &#125; &#125;'
title="Advanced: raw JSON controlling the file purpose dropdown options shown during upload. Leave empty to use system defaults."
bind:value={file_purpose_json_str} bind:value={file_purpose_json_str}
onblur={parse_file_purpose_json}></textarea> onblur={parse_file_purpose_json}></textarea>
{#if file_purpose_json_error} {#if file_purpose_json_error}
@@ -541,7 +598,9 @@ function toggle(key: string) {
{ slug: 'event_files', label: 'Event Files' }, { slug: 'event_files', label: 'Event Files' },
{ slug: 'file_downloads', label: 'File Downloads' } { slug: 'file_downloads', label: 'File Downloads' }
] as rpt (rpt.slug)} ] as rpt (rpt.slug)}
<label class="flex items-center gap-2"> <label
class="flex items-center gap-2"
title="Hides the '{rpt.label}' report tab from staff unless they are in edit mode.">
<input <input
type="checkbox" type="checkbox"
class="checkbox" class="checkbox"