- 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>
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_jsonis typed asanyinae_types.tsandkey_val | nullindb_events.ts— no canonical TypeScript interface exists.- Badge search and UI state still lives in
events_loc.badges(Svelte 4 nested store), with manualtypeof x === 'undefined'guards in+page.svelteandae_comp__badge_search.svelte. ae_events_stores__badges_defaults.tsalready has typedBadgesLocStateandBadgesSessStateinterfaces wired intoevents_loc— but these have not yet been promoted to a standalonePersistedStatelike pres_mgmt'spres_mgmt_loc.- The
edit_permissionssub-object (which controls which badge fields each access level may edit) is documented and wired up inae_comp__event_settings_badges_form.svelte, but the review page ([badge_id]/review/+page.svelte) still uses hardcoded defaults withTODOmarkers instead of reading frommod_badges_json. trusted_passcodeandadministrator_passcodeare stored inmod_badges_jsonand 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
- Canonical config schema — define
BadgesRemoteCfgTypeScript interface formod_badges_json - New Svelte 5 store — promote
events_loc.badgesto a standalonePersistedState(badges_loc) with its own localStorage key - Wire
edit_permissions— connect the review page tomod_badges_json.edit_permissions(remove hardcoded defaults) - Config UI — dedicated admin page at
(badges)/badges/config/for managingmod_badges_json - 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_VERSIONtostore_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:
- Load
lq__event_obj(already available via Dexie liveQuery in that page) - Derive
can_edit_fieldsfrom$lq__event_obj?.mod_badges_json?.edit_permissions - Fall back to the defaults from
BadgesRemoteCfgdefaults ifedit_permissionsis not set - The
ae_comp__badge_review_form.sveltecomponent interface is already correct — it acceptscan_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_jsonfresh 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
- Search & UI —
badge_id_only_search,enable_mass_print,enable_add_badge_btn,enable_upload_badge_li_btn,enable_search_qr - QR Config —
qr_type(text input) - Access Passcodes —
trusted_passcode,administrator_passcode(masked inputs; only visible to administrator_access) - Attendee Editable Fields —
edit_permissions.authenticated.can_edit(checkbox list per known field) - Staff Editable Fields —
edit_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.svelte → Badges (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_passcodeandadministrator_passcodeare sensitive credentials.- The config page must be gated at
administrator_access(not justmanager_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.
- The config page must be gated at
edit_permissionsaffects what data attendees can self-modify — changes take effect on the next page load (no caching concern since it's read frommod_badges_jsonon load).
Migration Path
Safe and backward compatible — the review page already falls back to hardcoded defaults.
- New
BadgesRemoteCfginterface — no DB changes needed ae_events_stores__badges.svelte.ts— new file, new localStorage key (ae_badges_loc)- Migrate
$events_loc.badges.*→badges_loc.current.*in two files (~48 refs) - Wire review page
can_edit_fieldstomod_badges_json.edit_permissions - Build config UI page and update settings page
Implementation Steps
- Step 1 — Define
BadgesRemoteCfgTypeScript interface (added toae_events_stores__badges_defaults.ts; also extracteddefault_authenticated_can_editanddefault_trusted_can_editconstants) - Step 2 — Created
ae_events_stores__badges.svelte.tswithPersistedState; addedAE_BADGES_LOC_VERSIONtostore_versions.ts - Step 3 — Migrated
$events_loc.badges.*→badges_loc.current.*in+page.svelteandae_comp__badge_search.svelte; removed all manualtypeofguards - Step 4 — Wired
edit_permissionsinto review pagecan_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
Badgessection 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 8 —
npx svelte-checkclean; commit
Implementation note (2026-04-02): Passcode fields use plain
type="text"inputs, nottype="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
BadgesLocStatealready has typed interfaces inae_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 inevents_sess.badgesinside the main store for now; it contains no persisted user prefs. enable_search_qrandqr_typeneed 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 fromdb_events.badge_templateliveQuery. - The
agree_to_tcfield incan_edit_fieldsis a placeholder — no Terms & Conditions flow exists yet. Gate it with a note in the UI.