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>
340 lines
13 KiB
Markdown
340 lines
13 KiB
Markdown
# 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`
|