259 lines
12 KiB
Markdown
259 lines
12 KiB
Markdown
# 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`:
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
// 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`
|
|
|
|
```typescript
|
|
// 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 & UI** — `badge_id_only_search`, `enable_mass_print`, `enable_add_badge_btn`, `enable_upload_badge_li_btn`, `enable_search_qr`
|
|
2. **QR Config** — `qr_type` (text input)
|
|
3. **Access Passcodes** — `trusted_passcode`, `administrator_passcode` (masked inputs; only visible to administrator_access)
|
|
4. **Attendee Editable Fields** — `edit_permissions.authenticated.can_edit` (checkbox list per known field)
|
|
5. **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:
|
|
|
|
```svelte
|
|
<!-- 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
|
|
|
|
- [x] **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)
|
|
- [x] **Step 2** — Created `ae_events_stores__badges.svelte.ts` with `PersistedState`; added `AE_BADGES_LOC_VERSION` to `store_versions.ts`
|
|
- [x] **Step 3** — Migrated `$events_loc.badges.*` → `badges_loc.current.*` in `+page.svelte` and `ae_comp__badge_search.svelte`; removed all manual `typeof` guards
|
|
- [x] **Step 4** — Wired `edit_permissions` into review page `can_edit_fields`; the two TODO blocks resolved
|
|
- [x] **Step 5** — Built config UI at `(badges)/badges/config/+page.svelte` (administrator_access gated)
|
|
- [x] **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
|
|
- [x] **Step 8** — `npx 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.
|