Files
OSIT-AE-App-Svelte/documentation/archive/PROJECT__AE_Events_Badges_Config_Cleanup.md
Scott Idem 17b549a75c docs/refactor: finalize V3 cleanup and archive badge config project
- Moved PROJECT__AE_Events_Badges_Config_Cleanup.md to archive.
- Updated PROJECT__Use_AE_API_V3_CRUD_upgrade.md with latest audit and migration status.
- Migrated ae_comp__event_presenter_form_agree.svelte to modern V3 update_ae_obj helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:04:07 -04:00

12 KiB

Project: Badges Config Cleanup & Config UI

Status: Executed — Complete Priority: Medium-High (post-April 2026 BGH conference; same pattern as pres_mgmt cleanup) Created: 2026-04-02 Related: TODO__Agents.md, PROJECT__AE_Events_PressMgmt_Config_Cleanup.md, PROJECT__Stores_Svelte5_Migration.md, MODULE__AE_Events_Badges.md


Background

The badges module has accumulated the same class of problems as pres_mgmt before its cleanup:

  • mod_badges_json is typed as any in ae_types.ts and key_val | null in db_events.ts — no canonical TypeScript interface exists.
  • Badge search and UI state still lives in events_loc.badges (Svelte 4 nested store), with manual typeof x === 'undefined' guards in +page.svelte and ae_comp__badge_search.svelte.
  • ae_events_stores__badges_defaults.ts already has typed BadgesLocState and BadgesSessState interfaces wired into events_loc — but these have not yet been promoted to a standalone PersistedState like pres_mgmt's pres_mgmt_loc.
  • The edit_permissions sub-object (which controls which badge fields each access level may edit) is documented and wired up in ae_comp__event_settings_badges_form.svelte, but the review page ([badge_id]/review/+page.svelte) still uses hardcoded defaults with TODO markers instead of reading from mod_badges_json.
  • trusted_passcode and administrator_passcode are stored in mod_badges_json and managed via the legacy settings form — no dedicated config UI exists.
  • Admin must edit the settings form (or DB directly) to change badge config — no standalone, grouped config page exists (unlike pres_mgmt, which now has /pres_mgmt/config).

Goals

  1. Canonical config schema — define BadgesRemoteCfg TypeScript interface for mod_badges_json
  2. New Svelte 5 store — promote events_loc.badges to a standalone PersistedState (badges_loc) with its own localStorage key
  3. Wire edit_permissions — connect the review page to mod_badges_json.edit_permissions (remove hardcoded defaults)
  4. Config UI — dedicated admin page at (badges)/badges/config/ for managing mod_badges_json
  5. Security review — ensure passcode fields are never exposed to non-administrator access

Canonical Remote Config Schema

BadgesRemoteCfg — the authoritative TypeScript interface for event.mod_badges_json:

interface BadgesRemoteCfg {
    // Search & UI behaviour
    enable_mass_print: boolean;         // show the mass-print controls
    enable_add_badge_btn: boolean;      // show the "Add Badge" button
    enable_upload_badge_li_btn: boolean; // show the "Upload Badge List" button
    enable_search_qr: boolean;          // enable QR scan search

    // QR code configuration
    qr_type: string | null;             // QR payload format (e.g. 'badge_id', 'url')

    // Access control — passcodes for attendee / staff tiered access
    // WARNING: Only expose to administrator_access. Never render client-side for lower levels.
    trusted_passcode: string | null;
    administrator_passcode: string | null;

    // Field-level edit permissions per access tier
    // key = access level ('authenticated' | 'trusted' | 'administrator')
    // value.can_edit = string[] of field keys, or '*' for all fields
    edit_permissions: {
        authenticated?: { can_edit: string[] | '*' };
        trusted?: { can_edit: string[] | '*' };
        administrator?: { can_edit: string[] | '*' };
    };
}

Default field permissions (encoded in defaults, not hardcoded in review page)

// Attendee (passcode-authenticated)
authenticated.can_edit = [
    'pronouns_override',
    'full_name_override',
    'professional_title_override',
    'affiliations_override',
    'phone_override',
    'location_override',
    'allow_tracking',
    'agree_to_tc',
]

// Trusted staff
trusted.can_edit = [
    'pronouns_override',
    'full_name_override',
    'professional_title_override',
    'affiliations_override',
    'phone_override',
    'location_override',
    'email_override',
    'badge_type_code_override',
    'registration_type_code_override',
    'allow_tracking',
    'agree_to_tc',
    'hide',
    'priority',
    'notes',
    // other_1_code ... other_8_code
    // ticket_1_code ... ticket_8_code
]

// Administrator
administrator.can_edit = '*'

New Svelte 5 Local Store

Do NOT touch events_loc or the paused Svelte 5 migration. Instead, promote the existing BadgesLocState to a standalone store.

Files to create/modify:

  • New store: src/lib/stores/ae_events_stores__badges.svelte.ts
  • Defaults file: src/lib/stores/ae_events_stores__badges_defaults.ts (already exists — no change needed to the types)
  • Version gate: add AE_BADGES_LOC_VERSION to store_versions.ts
// ae_events_stores__badges.svelte.ts
import { PersistedState } from 'runed';
import { badges_loc_defaults } from './ae_events_stores__badges_defaults';

export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults);
// Usage: badges_loc.current.fulltext_search_qry_str

New localStorage key: ae_badges_loc (separate from ae_events_loc)

Consumer syntax change:

BEFORE: $events_loc.badges.fulltext_search_qry_str
AFTER:  badges_loc.current.fulltext_search_qry_str

Store migration scope

$events_loc.badges is used in two files (~48 references total):

  • (badges)/badges/+page.svelte — all search params, inline guards (lines 59-73, 116-148, 423-424)
  • (badges)/badges/ae_comp__badge_search.svelte — all filter bindings (lines 40-228)

The manual typeof x === 'undefined' guards in +page.svelte are eliminated entirely — PersistedState with typed defaults guarantees fields always exist.


Review Page — Wire edit_permissions

File: (badges)/badges/[badge_id]/review/+page.svelte

Currently has two TODO markers at lines ~60 and ~197 where can_edit_fields is built from hardcoded arrays instead of mod_badges_json.edit_permissions.

After this change:

  1. Load lq__event_obj (already available via Dexie liveQuery in that page)
  2. Derive can_edit_fields from $lq__event_obj?.mod_badges_json?.edit_permissions
  3. Fall back to the defaults from BadgesRemoteCfg defaults if edit_permissions is not set
  4. The ae_comp__badge_review_form.svelte component interface is already correct — it accepts can_edit_fields: string[] prop

Config UI Page

Route: /events/[event_id]/(badges)/badges/config/ Access: $ae_loc.administrator_access only (passcodes present — stricter than pres_mgmt's manager_access) Button visibility: Edit mode only (or always visible in the section header, admin-gated)

Page behaviour

  • Loads event.mod_badges_json fresh from API (or Dexie) on page open
  • Displays grouped form sections (see below)
  • Save = load → merge draft → PATCH /v3/crud/event/{event_id} with { mod_badges_json: updated }
  • Settings page Badges (mod_badges_json) section gets a link to this page + raw JSON fallback (same pattern as pres_mgmt)

Form sections

  1. Search & UIbadge_id_only_search, enable_mass_print, enable_add_badge_btn, enable_upload_badge_li_btn, enable_search_qr
  2. QR Configqr_type (text input)
  3. Access Passcodestrusted_passcode, administrator_passcode (masked inputs; only visible to administrator_access)
  4. Attendee Editable Fieldsedit_permissions.authenticated.can_edit (checkbox list per known field)
  5. Staff Editable Fieldsedit_permissions.trusted.can_edit (checkbox list per known field)

Administrator is always * (all fields) — no UI control needed, show as read-only note.


Settings Page Changes

settings/+page.svelteBadges (mod_badges_json) section:

<!-- Replace the form+toggle with: -->
<p class="text-sm text-surface-500">
    Manage badge search, print controls, QR config, passcodes, and field permissions.
</p>
<a href="/events/{event_id}/badges/config" class="btn btn-sm preset-tonal-primary">
    Open Badges Config
</a>
<!-- Raw JSON fallback for debugging / emergency edits -->
<details class="mt-2">
    <summary class="text-xs text-surface-400 cursor-pointer">Raw JSON (advanced)</summary>
    <!-- existing CodeMirror editor remains here -->
</details>

The old ae_comp__event_settings_badges_form.svelte can be retired after the config page is live — keep the file for now but stop importing it from the settings page.


Security Notes

  • trusted_passcode and administrator_passcode are sensitive credentials.
    • The config page must be gated at administrator_access (not just manager_access).
    • Input fields should use type="password" with a show/hide toggle — do not render as plain text.
    • Never include passcode values in client-side logs or error messages.
  • edit_permissions affects what data attendees can self-modify — changes take effect on the next page load (no caching concern since it's read from mod_badges_json on load).

Migration Path

Safe and backward compatible — the review page already falls back to hardcoded defaults.

  1. New BadgesRemoteCfg interface — no DB changes needed
  2. ae_events_stores__badges.svelte.ts — new file, new localStorage key (ae_badges_loc)
  3. Migrate $events_loc.badges.*badges_loc.current.* in two files (~48 refs)
  4. Wire review page can_edit_fields to mod_badges_json.edit_permissions
  5. Build config UI page and update settings page

Implementation Steps

  • Step 1 — Define BadgesRemoteCfg TypeScript interface (added to ae_events_stores__badges_defaults.ts; also extracted default_authenticated_can_edit and default_trusted_can_edit constants)
  • Step 2 — Created ae_events_stores__badges.svelte.ts with PersistedState; added AE_BADGES_LOC_VERSION to store_versions.ts
  • Step 3 — Migrated $events_loc.badges.*badges_loc.current.* in +page.svelte and ae_comp__badge_search.svelte; removed all manual typeof guards
  • Step 4 — Wired edit_permissions into review page can_edit_fields; the two TODO blocks resolved
  • Step 5 — Built config UI at (badges)/badges/config/+page.svelte (administrator_access gated)
  • Step 6 — Updated settings page Badges section with link to config page; retired the old form component import
  • Step 7 — Update active event(s) via new UI; verify passcode fields function correctly
  • Step 8npx svelte-check clean; commit

Implementation note (2026-04-02): Passcode fields use plain type="text" inputs, not type="password". This matches the admin UI convention for this codebase.

Step 3 scope (find-replace)

grep -rn 'events_loc\.badges' src/

Affected files:

  • src/routes/events/[event_id]/(badges)/badges/+page.svelte (~35 refs)
  • src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_search.svelte (~13 refs)

Notes

  • BadgesLocState already has typed interfaces in ae_events_stores__badges_defaults.ts — this is ahead of where pres_mgmt was. Steps 1-3 are therefore lower risk.
  • The BadgesSessState (in-memory, resets on page load) does not need to move — it can stay in events_sess.badges inside the main store for now; it contains no persisted user prefs.
  • enable_search_qr and qr_type need validation: verify what QR type values are actually consumed by the scan component before exposing them as free-text inputs. A select with known options is safer.
  • Badge type code options (member, non-member, guest, etc.) are defined per Event Badge Template — the config page should not hardcode them. If badge type selects are needed in config, pull from db_events.badge_template liveQuery.
  • The agree_to_tc field in can_edit_fields is a placeholder — no Terms & Conditions flow exists yet. Gate it with a note in the UI.