feat(badges): print/review pages, 4-button list, Lucide icons, permissions doc
Badge search results list (ae_comp__badge_obj_li): - 4 action buttons per row: Print, Review (nav link), Copy Link (clipboard), Email Link - Visibility rules: unprinted-only for non-edit mode; all non-hidden for trusted+edit - Plain name display (User/EyeOff icon) — name is no longer a print link - Obscured email for non-trusted users - Debug row (ID, CR, UP, PC, FP, LP) in edit mode - All icons converted to Lucide (Font Awesome removed) Badge print page (/print): - 3 header action buttons: Print Now, Review (nav), Email Link - Removed old [badge_id]/+page.svelte placeholder (moved to trash) - Added is_trusted, is_edit_mode, print state derived vars - "Already printed Nx — last [timestamp]" warning inline with name - Removed unused imports (browser, onMount, events_slct) Badge review page (/review): - 3 header action buttons: Print (nav), Copy Link (clipboard), Email Link - Added events_loc for email placeholder + title event name - Added is_edit_mode, print_count, is_printed, copy_status - FA icons replaced with Lucide (ShieldCheck, UserCheck, User) - Title now includes event name (was missing) Infrastructure: - print/+page.ts and review/+page.ts added (non-blocking badge loaders) - ae_comp__badge_review_form.svelte stub created (fields pending) - Fixed: components no longer write to $ae_loc.edit_mode (critical bug) Docs: - NEW: AE__Permissions_and_Security.md — full permissions hierarchy reference - NEW: PROJECT__AE_Events_Badges_Review_Print.md — agent task brief for review form + print font controls - UPDATED: MODULE__AE_Events_Badges.md rev 5 — field permissions spec, header buttons, still-needed list by priority Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
174
documentation/AE__Permissions_and_Security.md
Normal file
174
documentation/AE__Permissions_and_Security.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# Aether — Permissions and Security
|
||||
|
||||
**Last updated:** 2026-02-27
|
||||
**Source of truth:** `src/lib/ae_utils/ae_utils__perm_checks.ts`, `src/lib/stores/ae_stores.ts`
|
||||
|
||||
---
|
||||
|
||||
## Access Level Hierarchy
|
||||
|
||||
Highest to lowest. Each level **inherits all access from every level below it**.
|
||||
|
||||
| Level | `access_type` string | Typical Use |
|
||||
| --- | --- | --- |
|
||||
| Super | `super` | OSIT internal — full system access |
|
||||
| Manager | `manager` | Account managers |
|
||||
| Administrator | `administrator` | Event/account admins |
|
||||
| Trusted | `trusted` | **Onsite staff** — site passcode or AE login |
|
||||
| Public | `public` | Site-wide passcode granted |
|
||||
| Authenticated | `authenticated` | Identity verified (e.g. IDAA Novi UUID) |
|
||||
| Anonymous | `anonymous` | Default — not signed in |
|
||||
|
||||
> **Note on Public vs Authenticated:** `public` is a *site-wide* unlock (anyone with the passcode). `authenticated` verifies a *specific identity*. In the hierarchy, public outranks authenticated because it implies broader site access.
|
||||
|
||||
---
|
||||
|
||||
## `$ae_loc` Store — Permission Flags
|
||||
|
||||
`$ae_loc` is a `persisted()` store (backed by localStorage). Key fields:
|
||||
|
||||
```typescript
|
||||
$ae_loc.access_type // string: current access type ('anonymous', 'trusted', etc.)
|
||||
|
||||
// Cumulative boolean flags (true = "you have AT LEAST this level")
|
||||
$ae_loc.anonymous_access // always true
|
||||
$ae_loc.authenticated_access // true from authenticated and above
|
||||
$ae_loc.public_access // true from public and above
|
||||
$ae_loc.trusted_access // true from trusted and above ← most-used gate
|
||||
$ae_loc.administrator_access // true from administrator and above
|
||||
$ae_loc.manager_access // true from manager and above
|
||||
$ae_loc.super_access // true only at super
|
||||
|
||||
// Exclusive check flags (true = "you are EXACTLY this level")
|
||||
$ae_loc.trusted_check // true only if access_type === 'trusted'
|
||||
$ae_loc.administrator_check // etc.
|
||||
// (rarely needed — prefer the _access flags)
|
||||
|
||||
// Behavior flags
|
||||
$ae_loc.edit_mode // boolean — user preference, see below
|
||||
$ae_loc.adv_mode // boolean — advanced mode toggle
|
||||
```
|
||||
|
||||
### Additional intermediate levels (in permission checks, not in hierarchy order)
|
||||
`support`, `assistant`, `verified`, `provisional` — appear in `_access` flags but are not part of the canonical `access_level_order`. Treat as internal/intermediate.
|
||||
|
||||
---
|
||||
|
||||
## Edit Mode — Critical Rules
|
||||
|
||||
`$ae_loc.edit_mode` is a **user preference**, not a permission level.
|
||||
|
||||
**Rules that must never be broken:**
|
||||
1. **Components must never write to `$ae_loc.edit_mode`** — only the system menu toggle and sign-out/permission-drop handlers may change it.
|
||||
2. Edit mode is only available to `trusted` and above in 95% of modules (the toggle is hidden from lower-access users).
|
||||
3. Edit mode persists across navigation — it is NOT reset by page loads or component mounts.
|
||||
4. Sign-out and permission drops to below `authenticated` should reset `edit_mode` to `false`.
|
||||
|
||||
> **Background:** A bug was fixed (2026-02-27) where `ae_comp__badge_obj_view.svelte` was writing `$ae_loc.edit_mode = false` in a data-loading `$effect`, silently overriding the user's preference on every navigation to the badge print page.
|
||||
|
||||
---
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
| Method | Grants | Used For |
|
||||
| --- | --- | --- |
|
||||
| Site passcode (`site_access_code_kv`) | `trusted`, `public`, or `authenticated` | Onsite staff and event attendees |
|
||||
| AE Username + Password | `trusted` and above | Staff with AE accounts |
|
||||
| Novi UUID | `authenticated` | IDAA members (Novi membership system) |
|
||||
|
||||
Passcodes are stored per-level in `$ae_loc.site_access_code_kv`:
|
||||
```typescript
|
||||
site_access_code_kv: {
|
||||
administrator: null, // highest passcode tier
|
||||
trusted: null, // onsite staff passcode
|
||||
public: 'public1980', // example
|
||||
authenticated: 'auth1980'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### `process_permission_checks(access_type: string)`
|
||||
Returns a full permission object (`_check` and `_access` flags) for a given access type string. Used when access type changes to update `$ae_loc`.
|
||||
|
||||
```typescript
|
||||
import { process_permission_checks } from '$lib/ae_utils/ae_utils__perm_checks';
|
||||
const checks = process_permission_checks('trusted');
|
||||
// checks.trusted_access === true
|
||||
// checks.administrator_access === false
|
||||
```
|
||||
|
||||
### `compare_access_levels(level_a, level_b)`
|
||||
Returns `1` if `level_a` is higher, `-1` if lower, `0` if equal. Useful for threshold comparisons.
|
||||
|
||||
---
|
||||
|
||||
## Privacy and Security Rules
|
||||
|
||||
### IDAA — International Doctors in Alcoholics Anonymous
|
||||
- **ALL IDAA content is private. Always. No exceptions.**
|
||||
- BB (Bulletin Board / Posts), Archives, Recovery Meetings — all require authentication.
|
||||
- IDAA users authenticate via Novi UUID at `authenticated` level or higher.
|
||||
- A prior agent accidentally exposed IDAA BB data publicly — treat any IDAA exposure as Sev-1.
|
||||
|
||||
### Journals
|
||||
- Private personal data. Always authenticated. Passcode/encryption features exist.
|
||||
- Never expose journal content publicly.
|
||||
|
||||
### `PUBLIC_AE_API_SECRET_KEY`
|
||||
- Ongoing Sev-1 audit. Do not introduce new usages.
|
||||
- Prefer per-request API key headers (`x-aether-api-key` + `x-account-id`).
|
||||
|
||||
### Email Display
|
||||
Non-trusted users must never see a full email address. Obscure using:
|
||||
```typescript
|
||||
// joh***@example.com
|
||||
function obscure_email(email: string): string {
|
||||
const at = email.indexOf('@');
|
||||
if (at < 0) return email;
|
||||
return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
|
||||
}
|
||||
```
|
||||
This pattern lives in `ae_comp__badge_obj_li.svelte` — move to `ae_utils` if needed elsewhere.
|
||||
|
||||
---
|
||||
|
||||
## Module-Specific Permission Patterns
|
||||
|
||||
### Events — Badges
|
||||
|
||||
| Scenario | Visibility | Print Action | Review Actions |
|
||||
| --- | --- | --- | --- |
|
||||
| Anonymous / below trusted | Unprinted only | None (name display only) | Email Review Link button (→ email API) |
|
||||
| Trusted, not Edit Mode | Unprinted only | Clickable (first print) | Email Review Link button |
|
||||
| Trusted, Edit Mode | All non-hidden | Clickable incl. reprint; shows `Nx` count | Email Review Link + direct Review Link (clipboard) |
|
||||
|
||||
- Print count badge: shown as `Nx` (e.g. `2×`) next to the printer icon when `print_count >= 1`
|
||||
- Edit mode for badges: limited to `trusted_access` users (toggle hidden from lower levels)
|
||||
- `person_passcode` field (for attendee-gated review URL): **not yet in DB** as of 2026-02-27
|
||||
|
||||
### IDAA
|
||||
- Auth gate test must be the **first test** in any test file — privacy enforcement is a hard requirement.
|
||||
- Default required permission: `trusted_access` or higher for module access.
|
||||
|
||||
---
|
||||
|
||||
## Common Template Patterns
|
||||
|
||||
```svelte
|
||||
<!-- Gate on trusted access -->
|
||||
{#if $ae_loc.trusted_access}
|
||||
|
||||
<!-- Gate on edit mode (always check trusted too — edit mode alone is insufficient) -->
|
||||
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}
|
||||
|
||||
<!-- Gate on administrator -->
|
||||
{#if $ae_loc.administrator_access}
|
||||
|
||||
<!-- Show full vs obscured email -->
|
||||
{$ae_loc.trusted_access ? email : obscure_email(email)}
|
||||
```
|
||||
|
||||
> Never gate purely on `$ae_loc.edit_mode` without also checking a permission level. Edit mode is a UI preference, not a permission grant.
|
||||
@@ -3,7 +3,7 @@
|
||||
**Module Path:** `src/routes/events/[event_id]/(badges)/badges/`
|
||||
**API Module:** `src/lib/ae_events/ae_events__event_badge.ts`
|
||||
**Database:** `db_events.badge` (Dexie IndexedDB table)
|
||||
**Last Updated:** 2026-02-26 (rev 3)
|
||||
**Last Updated:** 2026-02-27 (rev 5)
|
||||
|
||||
---
|
||||
|
||||
@@ -64,17 +64,23 @@ display_name = badge.full_name_override || badge.full_name || "-- no name --"
|
||||
// ✅ Display still shows "Bob Smith"
|
||||
```
|
||||
|
||||
### Override Fields (7 total)
|
||||
### Override Fields
|
||||
|
||||
| Regular Field | Override Field | Purpose | Editable By |
|
||||
|---|---|---|---|
|
||||
| `pronouns` | `pronouns_override` | Preferred pronouns | Staff, Attendee |
|
||||
| `professional_title` | `professional_title_override` | Job title display | Staff, Attendee |
|
||||
| `full_name` | `full_name_override` | Preferred name, pronouns | Staff, Attendee |
|
||||
| `full_name` | `full_name_override` | Preferred name display | Staff, Attendee |
|
||||
| `affiliations` | `affiliations_override` | Organization display | Staff, Attendee |
|
||||
| `phone` | `phone_override` | Phone number | Staff, Attendee |
|
||||
| `email` | `email_override` | Contact email override | Staff only |
|
||||
| `location` | `location_override` | City/State/Country display | Staff, Attendee |
|
||||
| `badge_type` | `badge_type_override` | Badge category label (varies per Event/Template) | Staff only |
|
||||
| `badge_type_code` | `badge_type_code_override` | Badge access level code (varies per Event/Template) | Staff only |
|
||||
| `badge_type` | `badge_type_override` | Badge category label text | Staff only |
|
||||
| `badge_type_code` | `badge_type_code_override` | Badge access level code | Staff only |
|
||||
| `registration_type` | `registration_type_override` | Registration category label text | Staff only |
|
||||
| `registration_type_code` | `registration_type_code_override` | Registration category code | Staff only |
|
||||
|
||||
> **Note:** `phone`, `phone_override`, `pronouns_override`, `registration_type`, `registration_type_code`, `registration_type_override`, `registration_type_code_override` may need to be confirmed against the DB schema via `ae_describe event_badge` and added to `properties_to_save` in `ae_events__event_badge.ts` if not already present.
|
||||
|
||||
### Sync Safety Rules
|
||||
|
||||
@@ -149,18 +155,39 @@ def sync_badge_from_external(external_badge_data, existing_badge):
|
||||
6. **Manager** — All administrator + event configuration
|
||||
7. **Super** — All manager + cross-event operations
|
||||
|
||||
### Current Implementation (v3)
|
||||
### Current Implementation (v3) — 2026-02-27
|
||||
|
||||
**Quick Edit Feature** ([badge_id]/+page.svelte → ae_comp__badge_obj_view.svelte)
|
||||
#### Badge Search Results Visibility
|
||||
|
||||
**Edit Mode Trigger:**
|
||||
- URL hash `#review` enables edit mode
|
||||
- Sets `$ae_loc.edit_mode = true`
|
||||
- Shows Save/Cancel buttons
|
||||
| Access Level | Sees |
|
||||
| --- | --- |
|
||||
| Below Trusted (incl. anonymous) | Only badges where `print_count < 1` and not hidden |
|
||||
| Trusted, not Edit Mode | Only badges where `print_count < 1` and not hidden |
|
||||
| Trusted + Edit Mode | All badges where `hide === false` (including already-printed) |
|
||||
|
||||
**Currently Editable Fields:**
|
||||
#### Print Button Behavior (per result row)
|
||||
|
||||
| Access Level | Print Action |
|
||||
| --- | --- |
|
||||
| Below Trusted | No print action — name shown with User icon, non-interactive |
|
||||
| Trusted, `print_count < 1` | Clickable link → `/print` page, Printer icon |
|
||||
| Trusted, `print_count >= 1`, not Edit Mode | Disabled (already printed safety lock), shows `Nx` count |
|
||||
| Trusted, `print_count >= 1`, Edit Mode | Clickable reprint — shows `Nx` count badge next to icon |
|
||||
|
||||
Print count displayed as `[Printer][2×] Name` when `print_count >= 1`.
|
||||
|
||||
#### Review Area Buttons (per result row, up to 3 buttons total)
|
||||
|
||||
| Button | Visible To | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Email Review Link | All users | Placeholder `alert()` — will trigger email API |
|
||||
| Review Link (clipboard) | Trusted + Edit Mode only | Copies `/review` URL to clipboard; shows `Copied!` feedback |
|
||||
| *(direct Review link)* | *(future)* | *(not yet implemented as separate nav button)* |
|
||||
|
||||
#### Badge Edit Form (`ae_comp__badge_obj_view.svelte`)
|
||||
|
||||
**Currently editable fields (local `edit_mode_active`, not global `edit_mode`):**
|
||||
```typescript
|
||||
// Lines 90-96 in ae_comp__badge_obj_view.svelte
|
||||
editable_full_name_override: string | null
|
||||
editable_professional_title_override: string | null
|
||||
editable_affiliations_override: string | null // textarea
|
||||
@@ -170,37 +197,109 @@ editable_email: string | null
|
||||
editable_badge_type_code: string | null
|
||||
```
|
||||
|
||||
**UI Components:**
|
||||
- Input fields shown when `edit_mode_active === true`
|
||||
- Save button calls `handle_save_changes()` (Line 365)
|
||||
- Cancel button calls `handle_cancel_changes()` (Line 450)
|
||||
- Only changed fields sent to API (`update_ae_obj__event_badge`)
|
||||
- Save button → `handle_save_changes()` — only changed fields sent to API
|
||||
- Cancel button → `handle_cancel_changes()` — reverts to IDB values
|
||||
- **IMPORTANT:** This component must NEVER write to `$ae_loc.edit_mode` — it uses its own local `edit_mode_active` flag only. (Bug fixed 2026-02-27)
|
||||
|
||||
### Future Planned Enhancement
|
||||
#### Badge Review Form (`ae_comp__badge_review_form.svelte`)
|
||||
|
||||
**Event-Level Configuration:** `event.mod_badges_json.edit_permissions`
|
||||
Form-based review (NOT a badge render). Used by the `/review` page.
|
||||
- `can_edit_fields: string[]` prop controls which fields are editable per user level
|
||||
- `['*']` = administrator (all fields)
|
||||
- `is_staff: boolean` prop shows/hides the source-data panel
|
||||
- Fields show "(overridden)" label when an override value differs from the base field
|
||||
|
||||
#### Badge Review Page — Header Buttons (implemented 2026-02-27)
|
||||
|
||||
| Button | Visible To | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Back → Search (ArrowLeft) | Staff (`has_staff_access`) only | `<a href="/events/{id}/badges">` |
|
||||
| Print (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | `<a href="/print">`, shows `Nx` count if reprinting |
|
||||
| Copy Link (clipboard) | Trusted + Edit Mode only | Copies review URL to clipboard; `Copied!` feedback for 2s |
|
||||
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
|
||||
|
||||
#### Badge Print Page — Header Buttons (implemented 2026-02-27)
|
||||
|
||||
| Button | Visible To | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Back → Search (ArrowLeft) | Always (when badge loaded) | `<a href="/events/{id}/badges">` |
|
||||
| Print Now (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | Calls `window.print()` directly (convenience duplicate); print count tracked by component button |
|
||||
| Review (Eye icon) | Trusted + Edit Mode only | `<a href="/review">` nav link |
|
||||
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
|
||||
|
||||
#### Badge Review Page — Display Sections (planned, not yet built)
|
||||
|
||||
In addition to the editable form, the review page will display:
|
||||
|
||||
1. **Print status** — print count + first/last print timestamps (read-only)
|
||||
2. **QR Code** — the attendee's badge QR code for scanning at the badge kiosk (for automatic badge search + print flow). QR generation code may be recoverable from the legacy AE Badge version.
|
||||
3. **Options** (`other_1_code` through `other_8_code`) — shown as `[✓] Option X` if the field has a value; hidden if empty
|
||||
4. **Tickets** (`ticket_1_code` through `ticket_8_code`) — shown as `[✓] Ticket X` if the field has a value; hidden if empty
|
||||
|
||||
#### Default Field Permissions (hardcoded for now — Axonius first show, mid-April 2026)
|
||||
|
||||
These are hardcoded in `review/+page.svelte` pending connection to `mod_badges_json.edit_permissions`.
|
||||
|
||||
**Attendee (passcode-authenticated / anonymous with link):**
|
||||
```typescript
|
||||
[
|
||||
'pronouns_override',
|
||||
'full_name_override',
|
||||
'professional_title_override',
|
||||
'affiliations_override',
|
||||
'phone_override',
|
||||
'location_override',
|
||||
'allow_tracking', // Exhibitor Leads opt-in
|
||||
'agree_to_tc', // Terms & Conditions placeholder
|
||||
]
|
||||
```
|
||||
|
||||
**Trusted Staff and above:**
|
||||
```typescript
|
||||
[
|
||||
'pronouns_override',
|
||||
'full_name_override',
|
||||
'professional_title_override',
|
||||
'affiliations_override',
|
||||
'email_override',
|
||||
'phone_override',
|
||||
'location_override',
|
||||
'badge_type_code_override', // + badge_type_override (text label)
|
||||
'registration_type_code_override', // + registration_type_override (text label)
|
||||
'option_1' ... 'option_8', // i.e. other_1_code ... other_8_code
|
||||
'ticket_1_code' ... 'ticket_8_code',
|
||||
'allow_tracking',
|
||||
'agree_to_tc',
|
||||
'hide',
|
||||
'priority',
|
||||
'notes',
|
||||
]
|
||||
```
|
||||
|
||||
**Administrator** — `can_edit_fields = ['*']` (all fields)
|
||||
|
||||
**Badge type options (hardcoded for now):** `member`, `non-member`, `guest`, `exhibitor`, `staff`, `test`
|
||||
(In future: read from Event Badge Template's configured list)
|
||||
|
||||
**Registration type options:** Same list as badge type for now — identical select options.
|
||||
|
||||
#### Future: Per-Event Configuration
|
||||
|
||||
`event.mod_badges_json.edit_permissions` — placeholder settings UI exists in
|
||||
`ae_comp__event_settings_badges_form.svelte`. Review page uses hardcoded defaults for now.
|
||||
The settings form and review page are not yet connected.
|
||||
|
||||
```json
|
||||
{
|
||||
"authenticated": {
|
||||
"can_edit": ["full_name_override", "professional_title_override", "affiliations_override", "location_override"],
|
||||
"requires_approval": false
|
||||
"can_edit": ["pronouns_override", "full_name_override", "professional_title_override", "affiliations_override", "phone_override", "location_override", "allow_tracking", "agree_to_tc"]
|
||||
},
|
||||
"trusted": {
|
||||
"can_edit": ["full_name_override", "professional_title_override", "affiliations_override", "location_override", "email_override"],
|
||||
"can_view_all": true,
|
||||
"requires_approval": false
|
||||
},
|
||||
"administrator": {
|
||||
"can_edit": "*",
|
||||
"can_bulk_edit": true,
|
||||
"can_delete": true
|
||||
"can_edit": ["*attendee_fields", "email_override", "badge_type_code_override", "registration_type_code_override", "option_x", "ticket_x_code", "allow_tracking", "agree_to_tc", "hide", "priority", "notes"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Status:** Placeholder UI exists, JSON config not yet implemented.
|
||||
|
||||
---
|
||||
|
||||
## Search & Filter Capabilities
|
||||
@@ -377,15 +476,14 @@ async function handle_print_badge() {
|
||||
Button has `data-testid="badge-print-btn"` and shows loading/done/error states with icon feedback.
|
||||
|
||||
### Print Workflow
|
||||
1. **Pre-Print:** Check `print_count` to warn if already printed
|
||||
2. **Print:** `window.print()` — standard browser print dialog. Works well in Chrome, Chromium, and Firefox. Chrome is the recommended browser for onsite badge printing (most stable in production).
|
||||
3. **Post-Print:** Handled by `handle_print_badge()` — count + timestamps updated
|
||||
4. **Audit:** Print history available for staff review
|
||||
1. **Pre-Print:** Badge print page (`/print`) shows "Already printed N times" warning in screen-only header if `print_count >= 1`
|
||||
2. **Record:** `handle_print_badge()` updates `print_count`, `print_last_datetime`, and `print_first_datetime` (first print only) via API before printing
|
||||
3. **Print:** `window.print()` — standard browser print dialog, wired and working (2026-02-27)
|
||||
4. **Redirect:** After 1 second, `goto(/events/{id}/badges)` returns to search
|
||||
5. **Audit:** `print_first_datetime` and `print_last_datetime` visible in Edit Mode debug row
|
||||
|
||||
**Browser vs Electron:** Badge printing does NOT require the Electron native app. The standard browser print dialog works well across Chrome, Chromium, and Firefox. The Electron native app is specialized for the **Events Pres Mgmt Launcher only** and should not be assumed available for badge stations.
|
||||
|
||||
**Current Status:** Button records print event and updates IDB. The `window.print()` call still needs to be wired to the button — see Future Enhancements.
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
@@ -483,18 +581,42 @@ delete_ae_obj_id__event_badge({ event_badge_id, event_id, method })
|
||||
### Route Structure
|
||||
```
|
||||
/events/[event_id]/(badges)/badges/
|
||||
├── +layout.svelte # Layout wrapper (minimal)
|
||||
├── +page.svelte # Badge list + search
|
||||
├── ae_comp__badge_search.svelte # Search form + filters
|
||||
├── ae_comp__badge_obj_li.svelte # Badge list display
|
||||
├── +layout.svelte # Layout wrapper (minimal)
|
||||
├── +page.svelte # Badge list + search
|
||||
├── ae_comp__badge_search.svelte # Search form + filters
|
||||
├── ae_comp__badge_obj_li.svelte # Badge list display (results)
|
||||
├── ae_comp__badge_create_form.svelte # (Not actively used)
|
||||
├── ae_comp__badge_upload_form.svelte # Bulk CSV upload
|
||||
└── [badge_id]/
|
||||
├── +page.svelte # Badge detail container
|
||||
├── +page.ts # Badge loader (non-blocking)
|
||||
└── ae_comp__badge_obj_view.svelte # Badge display + edit
|
||||
├── ae_comp__badge_obj_view.svelte # Badge rendering + staff edit + print button
|
||||
├── ae_comp__badge_review_form.svelte # Form-based field review/edit (attendee + staff)
|
||||
├── print/
|
||||
│ ├── +page.ts # Non-blocking badge loader (inc_template: true)
|
||||
│ └── +page.svelte # Print-focused page — screen header + badge render
|
||||
└── review/
|
||||
├── +page.ts # Non-blocking badge loader (inc_template: false)
|
||||
└── +page.svelte # Passcode-gated review page
|
||||
```
|
||||
|
||||
> **Note:** The old `[badge_id]/+page.svelte` placeholder was removed (2026-02-27). The name link in the search results list now goes directly to `/print`.
|
||||
|
||||
#### Badge Print Page (`/print`)
|
||||
- Screen-only header (`print:hidden`): "Back to Search" link + "Already printed N times" warning
|
||||
- Badge rendered via `ae_comp__badge_obj_view` with `is_review_mode={false}`
|
||||
- Print button inside `ae_comp__badge_obj_view` handles count update → `window.print()` → redirect to search
|
||||
- Page `<title>` includes badge name + event name
|
||||
|
||||
#### Badge Review Page (`/review`)
|
||||
- Passcode-gated for attendees — URL `?passcode=...` matched against `badge.person_passcode`
|
||||
- **Note:** `person_passcode` field is not yet in the DB (as of 2026-02-27). Review page accessible to staff via `trusted_access` without a passcode.
|
||||
- Access hierarchy (checked in order):
|
||||
1. Administrator → full access (`can_edit_fields = ['*']`)
|
||||
2. Trusted Staff → staff field set
|
||||
3. Attendee with valid passcode → attendee field set
|
||||
4. No access → passcode entry form shown
|
||||
- Uses `ae_comp__badge_review_form.svelte` (NOT badge render)
|
||||
- "Back to Search" link shown for staff only
|
||||
|
||||
### Key Components
|
||||
|
||||
**Badge List Page** (`+page.svelte`)
|
||||
@@ -577,19 +699,45 @@ None — all current badge tests passing as of 2026-02-26 (f5e98b8c).
|
||||
## Known Issues & Future Enhancements
|
||||
|
||||
### Known Issues
|
||||
1. **Test Infrastructure:** Mock API routes not connecting to page requests
|
||||
2. **Session Cold-Start:** Potential race condition on first load (same as pres mgmt module)
|
||||
3. **Type Definitions:** Some TypeScript errors on external package types (pre-existing)
|
||||
1. **Session Cold-Start:** Potential race condition on first load (same as pres mgmt module)
|
||||
2. **Type Definitions:** Some pre-existing TypeScript errors on external package types (not introduced by badge work)
|
||||
3. **`person_passcode` not in DB:** Attendee-gated review URL (`?passcode=...`) cannot function until this field is added to the `event_badge` schema. The review page falls back to passcode entry form for non-staff.
|
||||
4. **Print page CSS:** Badge print rendering and `@page` print styles not yet fine-tuned — expected to need work
|
||||
5. **`mod_badges_json.edit_permissions` not connected:** Settings UI exists but review page uses hardcoded field defaults
|
||||
|
||||
### Future Enhancements
|
||||
1. **Access-Based Edit Permissions:** Implement JSON config for field-level access control
|
||||
2. **Print Functionality:** Wire up `window.print()` to the print button. Standard browser print API — works well in Chrome/Chromium/Firefox for badge label printing. Electron is NOT needed.
|
||||
3. **Batch Operations:** Bulk update, bulk print, bulk export
|
||||
4. **Audit Log:** Track who edited which fields and when
|
||||
5. **Photo Badges:** Support badge photo upload and display
|
||||
6. **Custom Badge Layouts:** Dynamic template selection per badge type
|
||||
7. **Real-Time Sync:** WebSocket updates for multi-device badge printing stations
|
||||
8. **Approval Workflow:** Require manager approval for certain field changes
|
||||
### Implemented (2026-02-27)
|
||||
- ✅ `window.print()` wired to print button (records count first, then prints, then redirects)
|
||||
- ✅ Dedicated `/print` page — replaces old `[badge_id]/+page.svelte` placeholder
|
||||
- ✅ Dedicated `/review` page — passcode-gated, access-tiered
|
||||
- ✅ `ae_comp__badge_review_form.svelte` — stub created, full form fields pending
|
||||
- ✅ Badge search results visibility rules (unprinted-only for non-edit, all for trusted+edit)
|
||||
- ✅ Badge list: 4 action buttons per row (Print, Review nav, Copy Link, Email Link) — all Lucide icons
|
||||
- ✅ Print page: 3 action buttons in header (Print Now, Review nav, Email Link) — all Lucide icons
|
||||
- ✅ Review page: 3 action buttons in header (Print nav, Copy Link, Email Link) — all Lucide icons
|
||||
- ✅ Print button: not shown when already printed (unless Edit Mode)
|
||||
- ✅ Print count shown as `Nx` badge next to printer icon
|
||||
- ✅ Email obscuring for non-trusted users
|
||||
- ✅ Email Review Link button (placeholder alert — email API pending)
|
||||
- ✅ Direct Review Link clipboard copy (trusted + Edit Mode only)
|
||||
- ✅ Fixed: components no longer write to `$ae_loc.edit_mode`
|
||||
- ✅ Settings UI for `edit_permissions` per event (`ae_comp__event_settings_badges_form.svelte`)
|
||||
- ✅ All badge module icons converted to Lucide (Font Awesome removed from badge routes)
|
||||
|
||||
### Still Needed — HIGH PRIORITY (first show: April 2026)
|
||||
1. **Badge Review Form — actual fields:** `ae_comp__badge_review_form.svelte` is a stub. Needs full field rendering, edit inputs, save/cancel, and the display-only sections (QR code, print status, option/ticket checkmarks). See `PROJECT__AE_Events_Badges_Review_Print.md` for full spec.
|
||||
2. **Badge Print Page — font size controls:** Screen-only controls to adjust font size for name, professional title, affiliations, and location sections before printing. See project brief.
|
||||
|
||||
### Still Needed — MEDIUM PRIORITY
|
||||
1. **Email API for review links:** `send_review_email()` is a placeholder `alert()`. Needs actual email send endpoint.
|
||||
2. **`person_passcode` DB field:** Add to `event_badge` schema to enable attendee-gated review URLs.
|
||||
3. **Connect `edit_permissions` config:** Read `mod_badges_json.edit_permissions` in review page instead of hardcoded defaults.
|
||||
4. **Print page CSS / `@page` styles:** Badge rendering, sizing, and print-specific stylesheet.
|
||||
|
||||
### Still Needed — FUTURE / LOW PRIORITY
|
||||
1. **Batch Operations:** Bulk update, bulk print, bulk export
|
||||
2. **Audit Log:** Track who edited which fields and when
|
||||
3. **Photo Badges:** Support badge photo upload and display
|
||||
4. **Real-Time Sync:** WebSocket updates for multi-device badge printing stations
|
||||
|
||||
---
|
||||
|
||||
@@ -646,6 +794,6 @@ db_events.badge.toArray().then(console.log)
|
||||
|
||||
---
|
||||
|
||||
**Document Status:** ✅ Complete
|
||||
**Last Verified:** 2026-02-26 (rev 3 — all badge tests passing)
|
||||
**Verified Against:** Code commit f5e98b8c (all data integrity tests passing)
|
||||
**Document Status:** 🔄 In Progress
|
||||
**Last Verified:** 2026-02-27 (rev 5 — field permissions spec added, header buttons implemented, review form fields pending)
|
||||
**Verified Against:** Code as of 2026-02-27 (branch ae_app_3x_llm)
|
||||
|
||||
339
documentation/PROJECT__AE_Events_Badges_Review_Print.md
Normal file
339
documentation/PROJECT__AE_Events_Badges_Review_Print.md
Normal file
@@ -0,0 +1,339 @@
|
||||
# PROJECT: AE Events Badges — Review Form & Print Font Controls
|
||||
|
||||
**Created:** 2026-02-27
|
||||
**Branch:** `ae_app_3x_llm`
|
||||
**Priority:** HIGH — first live event is Axonius, NYC, mid-April 2026
|
||||
**Owner:** Scott Idem / One Sky IT
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The Events Badges module is mostly complete for navigation and search. Two key pieces of
|
||||
functional UI remain unbuilt and are needed before the first show:
|
||||
|
||||
1. **Badge Review Form** — `ae_comp__badge_review_form.svelte` is currently a stub. It
|
||||
needs actual field rendering, edit inputs gated by access level, save/cancel API calls,
|
||||
and display-only sections (QR code, print status, option/ticket checkmarks).
|
||||
|
||||
2. **Badge Print Font Controls** — The print page header needs screen-only controls
|
||||
(hidden during `window.print()`) to bump font sizes for the name, professional title,
|
||||
affiliations, and location sections before printing. These only affect the `ae_comp__badge_obj_view.svelte` render — not the page layout/template structural dimensions.
|
||||
|
||||
Read `documentation/MODULE__AE_Events_Badges.md` for full module context before starting.
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY: Before You Start
|
||||
|
||||
1. Run `ae_describe event_badge` (MCP tool) to confirm which fields actually exist in the
|
||||
DB. Several fields in the spec below may need to be added to `properties_to_save` in
|
||||
`src/lib/ae_events/ae_events__event_badge.ts` if they are not already saved to IDB.
|
||||
|
||||
2. Fields to specifically confirm exist in `event_badge` schema:
|
||||
- `pronouns`, `pronouns_override`
|
||||
- `phone`, `phone_override`
|
||||
- `allow_tracking`
|
||||
- `agree_to_tc`
|
||||
- `other_1_code` through `other_8_code` (the "option" fields)
|
||||
- `ticket_1_code` through `ticket_8_code`
|
||||
- `registration_type`, `registration_type_code`
|
||||
- `registration_type_override`, `registration_type_code_override`
|
||||
|
||||
3. Run `npx svelte-check` before committing. Baseline is **77 errors** (all pre-existing,
|
||||
none in the badge module files). Do not introduce new errors.
|
||||
|
||||
4. Do NOT write to `$ae_loc.edit_mode` from any badge component. This was a critical
|
||||
bug (fixed 2026-02-27). See `documentation/AE__Permissions_and_Security.md`.
|
||||
|
||||
---
|
||||
|
||||
## TASK 1: Badge Review Form (HIGH PRIORITY)
|
||||
|
||||
### File to build
|
||||
`src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_review_form.svelte`
|
||||
|
||||
This component is already imported and used by `review/+page.svelte`. Props it receives:
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
event_id: string;
|
||||
event_badge_id: string;
|
||||
lq__event_badge_obj: any; // Svelte 5 store from liveQuery
|
||||
can_edit_fields: string[]; // Which fields this user can edit
|
||||
is_staff: boolean; // True if trusted_access or higher
|
||||
log_lvl?: number;
|
||||
}
|
||||
```
|
||||
|
||||
`can_edit_fields` values:
|
||||
- `['*']` — administrator (all fields)
|
||||
- Array of field names — specific editable fields
|
||||
- `[]` — read-only (shouldn't normally reach this component, but handle it)
|
||||
|
||||
### Helper
|
||||
|
||||
Use a helper derived inside the component:
|
||||
```typescript
|
||||
function can_edit(field: string): boolean {
|
||||
return can_edit_fields.includes('*') || can_edit_fields.includes(field);
|
||||
}
|
||||
```
|
||||
|
||||
### Save / Cancel Pattern
|
||||
|
||||
Follow the Journals module pattern (`src/lib/ae_journals/`). Key points:
|
||||
- Use `import { events_func } from '$lib/ae_events_functions'`
|
||||
- Call `events_func.update_ae_obj__event_badge({ event_badge_id, event_id, data_kv })`
|
||||
- Only send changed fields in `data_kv` (compare against `$lq__event_badge_obj` values)
|
||||
- Show save/cancel buttons only when something has changed (`has_changes` derived)
|
||||
- Show a success/error state briefly after save (1-2 seconds, then reset)
|
||||
- Cancel resets local state back to `$lq__event_badge_obj` values
|
||||
- Use `data-testid="badge-review-save-btn"` and `data-testid="badge-review-cancel-btn"`
|
||||
|
||||
### Save API Call
|
||||
|
||||
```typescript
|
||||
await events_func.update_ae_obj__event_badge({
|
||||
api_cfg: $ae_api, // from ae_loc store or passed as prop — check how ae_comp__badge_obj_view.svelte does it
|
||||
event_badge_id: event_badge_id,
|
||||
event_id: event_id,
|
||||
data_kv: { /* only changed fields */ }
|
||||
});
|
||||
```
|
||||
|
||||
Check `ae_comp__badge_obj_view.svelte` for the existing save pattern — it already works
|
||||
and can be used as reference.
|
||||
|
||||
---
|
||||
|
||||
### Section 1: Display-Only Status Bar (all access levels)
|
||||
|
||||
Always show at top of form. Read-only. No edit controls.
|
||||
|
||||
```
|
||||
Print Status: [Not yet printed] OR [Printed 3× — first: Jan 5 2026, last: Jan 5 2026]
|
||||
```
|
||||
|
||||
Use `$lq__event_badge_obj.print_count`, `print_first_datetime`, `print_last_datetime`.
|
||||
Format datetimes with `ae_util.iso_datetime_formatter(dt, 'datetime_iso_12_no_seconds')`.
|
||||
Import `ae_util` from `$lib/ae_utils/ae_utils`.
|
||||
|
||||
---
|
||||
|
||||
### Section 2: QR Code (all access levels)
|
||||
|
||||
Display the attendee's badge QR code. This is the same QR code shown on the printed badge
|
||||
itself — scanning it at the badge station triggers automatic badge search and print.
|
||||
|
||||
**Check the legacy AE Badge version for existing QR generation code.** Look in:
|
||||
- `src/lib/ae_events/` for any QR-related utilities
|
||||
- `ae_comp__badge_obj_view.svelte` — the badge render component almost certainly generates
|
||||
a QR code already for the printed badge. Reuse that logic/component if possible.
|
||||
|
||||
The QR code value should encode the badge ID or a URL that resolves to the badge.
|
||||
|
||||
---
|
||||
|
||||
### Section 3: Editable Fields
|
||||
|
||||
Render each field as: read-only display when `!can_edit(field)`, or an `<input>` /
|
||||
`<select>` / `<textarea>` when `can_edit(field)`.
|
||||
|
||||
Show `(overridden)` label next to override fields when the override value differs from
|
||||
the base field value.
|
||||
|
||||
#### Attendee-Editable Fields (shown to all access levels with link)
|
||||
|
||||
| Field | Input Type | Notes |
|
||||
|---|---|---|
|
||||
| `pronouns_override` | text input | Fallback display: `pronouns` |
|
||||
| `full_name_override` | text input | Fallback display: `full_name` |
|
||||
| `professional_title_override` | text input | Fallback display: `professional_title` |
|
||||
| `affiliations_override` | textarea | Fallback display: `affiliations` |
|
||||
| `phone_override` | text input (tel) | Fallback display: `phone` |
|
||||
| `location_override` | text input | Fallback display: `location` |
|
||||
| `allow_tracking` | checkbox | Label: "Allow exhibitor lead scanning" |
|
||||
| `agree_to_tc` | checkbox | Label: "I agree to the Terms and Conditions" + placeholder T&C text block |
|
||||
|
||||
#### Staff-Only Additional Fields (shown when `is_staff === true`)
|
||||
|
||||
| Field | Input Type | Notes |
|
||||
|---|---|---|
|
||||
| `email_override` | email input | Fallback display: `email` |
|
||||
| `badge_type_code_override` | select | Options: member, non-member, guest, exhibitor, staff, test; also updates `badge_type_override` text |
|
||||
| `registration_type_code_override` | select | Same options as badge_type for now; also updates `registration_type_override` |
|
||||
| `hide` | checkbox | Label: "Hidden from search results" |
|
||||
| `priority` | number input | |
|
||||
| `notes` | textarea | |
|
||||
|
||||
#### Staff-Only: Options & Tickets (read-edit, shown when `is_staff === true`)
|
||||
|
||||
**Other/Options** (`other_1_code` through `other_8_code`):
|
||||
- If field has a value: show as editable text input with label "Option X"
|
||||
- If field is empty/null: show faintly as "Option X (empty)" — staff can still set it
|
||||
- These represent event-specific add-ons or membership indicators
|
||||
|
||||
**Tickets** (`ticket_1_code` through `ticket_8_code`):
|
||||
- Same pattern as options above, label "Ticket X"
|
||||
|
||||
#### Attendee-Only: Options & Tickets (display only)
|
||||
|
||||
When `!is_staff` and the field has a value: show `[✓] Option X` or `[✓] Ticket X`.
|
||||
When the field is empty: hide entirely (attendees don't see empty slots).
|
||||
|
||||
---
|
||||
|
||||
### Section 4: Terms & Conditions Block (all, only when `agree_to_tc` in can_edit_fields)
|
||||
|
||||
Placeholder text for now:
|
||||
```
|
||||
By checking this box, I confirm that the information on my badge is correct to the best
|
||||
of my knowledge. I agree that this badge may be used for identification purposes during
|
||||
the event and that my attendance may be recorded by exhibitors using the lead scanning
|
||||
feature if I permit it.
|
||||
```
|
||||
|
||||
Show this before the `agree_to_tc` checkbox. If `agree_to_tc` is not in `can_edit_fields`,
|
||||
hide the entire block.
|
||||
|
||||
---
|
||||
|
||||
### Field State Pattern (Svelte 5 runes)
|
||||
|
||||
```typescript
|
||||
// Initialize local editable state from badge object
|
||||
let local_full_name_override = $state($lq__event_badge_obj?.full_name_override ?? '');
|
||||
let local_pronouns_override = $state($lq__event_badge_obj?.pronouns_override ?? '');
|
||||
// ... etc for each editable field
|
||||
|
||||
// Detect changes
|
||||
let has_changes = $derived(
|
||||
local_full_name_override !== ($lq__event_badge_obj?.full_name_override ?? '')
|
||||
|| local_pronouns_override !== ($lq__event_badge_obj?.pronouns_override ?? '')
|
||||
// ... etc
|
||||
);
|
||||
|
||||
// Build changed-fields-only payload
|
||||
function build_save_payload(): Record<string, any> {
|
||||
const payload: Record<string, any> = {};
|
||||
if (local_full_name_override !== ($lq__event_badge_obj?.full_name_override ?? ''))
|
||||
payload.full_name_override = local_full_name_override || null; // empty string → null
|
||||
// ... etc
|
||||
return payload;
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Empty string inputs should save as `null` (clears the override, falls back
|
||||
to base field). Use `value || null` in the payload.
|
||||
|
||||
---
|
||||
|
||||
## TASK 2: Badge Print Font Size Controls (MEDIUM PRIORITY)
|
||||
|
||||
### Where to add
|
||||
|
||||
`src/routes/events/[event_id]/(badges)/badges/[badge_id]/print/+page.svelte`
|
||||
|
||||
Add a screen-only (`print:hidden`) control panel between the header and the badge render.
|
||||
This panel lets staff adjust font sizes for the four text-heavy sections before clicking Print.
|
||||
|
||||
### Controls needed
|
||||
|
||||
```
|
||||
Font Size Controls (screen only, hidden during print):
|
||||
[Name] [−] [14px] [+]
|
||||
[Title] [−] [12px] [+]
|
||||
[Affiliations] [−] [11px] [+]
|
||||
[Location] [−] [10px] [+]
|
||||
```
|
||||
|
||||
- Start with sensible defaults (match what `ae_comp__badge_obj_view.svelte` currently uses)
|
||||
- Min/max per field (e.g., 8px–24px for name, 7px–18px for others)
|
||||
- Pass the sizes as props into `ae_comp__badge_obj_view`
|
||||
|
||||
### Props to add to `ae_comp__badge_obj_view.svelte`
|
||||
|
||||
`ae_comp__badge_obj_view.svelte` currently has internal font size logic. It needs to
|
||||
accept optional override props:
|
||||
|
||||
```typescript
|
||||
// New optional props:
|
||||
font_size_name?: number; // px
|
||||
font_size_title?: number; // px
|
||||
font_size_affiliations?: number; // px
|
||||
font_size_location?: number; // px
|
||||
```
|
||||
|
||||
When these props are provided, use them instead of the internally computed sizes.
|
||||
When not provided, fall back to existing auto-sizing behavior.
|
||||
|
||||
**IMPORTANT:** Do NOT touch structural dimensions (overall badge width/height, header/footer
|
||||
sizes, template layout). Only the text content font sizes.
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `[badge_id]/ae_comp__badge_review_form.svelte` | **BUILD THIS** — review form stub |
|
||||
| `[badge_id]/ae_comp__badge_obj_view.svelte` | Badge render + print button; add font size props |
|
||||
| `[badge_id]/print/+page.svelte` | Print page; add font size control panel |
|
||||
| `[badge_id]/review/+page.svelte` | Review page; already wired, passes `can_edit_fields` |
|
||||
| `src/lib/ae_events/ae_events__event_badge.ts` | API functions: `update_ae_obj__event_badge` |
|
||||
| `src/lib/ae_events/db_events.ts` | Dexie schema — `properties_to_save` for badge |
|
||||
| `src/lib/ae_utils/ae_utils.ts` | `ae_util.iso_datetime_formatter()` |
|
||||
| `documentation/MODULE__AE_Events_Badges.md` | Full module reference |
|
||||
| `documentation/AE__Permissions_and_Security.md` | Permission flags, edit_mode rules |
|
||||
| `documentation/GUIDE__AE_API_V3_for_Frontend.md` | V3 API reference |
|
||||
|
||||
## Access Level Reference
|
||||
|
||||
```typescript
|
||||
// From $ae_loc store (persisted localStorage)
|
||||
$ae_loc.trusted_access // true = trusted and above (onsite staff)
|
||||
$ae_loc.administrator_access // true = administrator and above
|
||||
$ae_loc.edit_mode // boolean — user preference toggle (NEVER write to this from components)
|
||||
```
|
||||
|
||||
`is_staff` prop on the review form = `administrator_access || trusted_access`.
|
||||
|
||||
---
|
||||
|
||||
## Patterns to Follow
|
||||
|
||||
- **Canonical module reference:** `src/lib/ae_journals/` — most complete, most advanced
|
||||
- **Svelte 5 runes:** `$state`, `$derived`, `$derived.by()`, `$effect` — no legacy `$:` syntax
|
||||
- **Icons:** Lucide Svelte only — `import { Save, X, Check, ... } from 'lucide-svelte'`
|
||||
- **No Font Awesome** (`fas fa-*`) anywhere in the badge module
|
||||
- **Styling:** Tailwind CSS v4 + Skeleton UI utility classes (`btn`, `preset-tonal-*`, `input`, `card`)
|
||||
- **Commits:** Atomic — one component per commit; run `npx svelte-check` before every commit
|
||||
|
||||
---
|
||||
|
||||
## What NOT to Do
|
||||
|
||||
- Do NOT touch `@page` CSS or badge template structural dimensions — print layout is out of scope
|
||||
- Do NOT write to `$ae_loc.edit_mode` from any component
|
||||
- Do NOT connect `mod_badges_json.edit_permissions` yet — hardcoded field lists are intentional for now
|
||||
- Do NOT implement the email API — `send_review_email()` placeholder stays as `alert()`
|
||||
- Do NOT add `person_passcode` DB field — out of scope for this sprint
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Run existing badge tests after any changes:
|
||||
```bash
|
||||
npm run test:unit
|
||||
npx playwright test tests/events/badges/
|
||||
```
|
||||
|
||||
Baseline: all badge tests passing as of 2026-02-26 (`f5e98b8c`).
|
||||
|
||||
Add `data-testid` attributes to key interactive elements:
|
||||
- `badge-review-save-btn`
|
||||
- `badge-review-cancel-btn`
|
||||
- `badge-review-full-name-input`
|
||||
- `badge-review-agree-to-tc-checkbox`
|
||||
Reference in New Issue
Block a user