# PROJECT: AE Events Badges — Review Form & Print Font Controls

**Created:** 2026-02-27
**Last Updated:** 2026-03-18
**Branch:** `ae_app_3x_llm`
**Priority:** HIGH — first live event is Axonius, NYC, mid-April 2026
**Owner:** Scott Idem / One Sky IT
**Status:** ✅ TASK 1 COMPLETE | ✅ TASK 2 COMPLETE | ✅ TASK 3 COMPLETE | ✅ TASK 4.1 COMPLETE | ⏳ TASK 4.0 OPEN

---

## Design Intent — Two Complementary Flows

### Flow 1: Remote Badge Review (email link)
- Staff emails a review link to the attendee before the event.
- Attendee opens the link on their own device, reviews their badge info, and edits permitted fields.
- **Email address rule:** Always send to `event_badge.email` — never `email_override`.
  `email_override` is a display/badge field only. It cannot be trusted as a delivery address
  (attendee may have changed it to something different for badge display purposes).
- Component: `ae_comp__badge_review_form.svelte` — plain form, no badge render.
- Route: `/events/[event_id]/badges/[badge_id]/review/`

### Flow 2: Kiosk / Onsite Badge Station (print page)
- Hardware: a laptop + badge printer (Epson fanfold or Zebra PVC card) at the check-in table.
- At the event, an attendee walks up to a badge station (check-in kiosk).
- A staff member or volunteer pulls up the attendee's badge on the print page.
- The **print page is a kiosk tool**, not just a print queue:
  - Attendee reviews their badge info and can edit permitted fields **in real time**,
    with the live badge render updating as they make changes.
  - Staff/volunteers are present to assist with any questions.
  - Once satisfied, staff prints the badge.
- The key differentiator vs the review form: **the live badge render** shows exactly how
  the badge will print. Attendees and staff can see changes immediately.
- Component: `ae_comp__badge_obj_view.svelte`
- Route: `/events/[event_id]/badges/[badge_id]/print/`

### Permission Model — Same Logic, Both Flows
Both flows should respect the same permission model:
- **Attendee-level** (basic Authenticated access): can edit `pronouns_override`,
  `full_name_override`, `professional_title_override`, `affiliations_override`,
  `location_override`, `phone_override`, `email_override`, `allow_tracking`, `agree_to_tc`.
- **Staff-level** (trusted_access+): all attendee fields + `email`, `badge_type_code_override`,
  `badge_type_override`, `hide`, `priority`, `notes`, and font size controls.
- Permissions are configured per-event in `event.mod_badges_json.edit_permissions`.
  Hardcoded defaults are used until that config is implemented.

**Current gap (TASK 4):** The print page edit button is currently gated to trusted_access only.
It needs to be accessible to attendees at the kiosk (with appropriate field-level gating),
matching the permission model already implemented in `ae_comp__badge_review_form.svelte`.

---

## Next Up for Badges (TASK 4)

### 0. Kiosk Editing — Print Page Permission Model Alignment
**This is the most important gap before the first live event.**

Currently the print page edit button is staff-only (trusted_access gate). At the kiosk,
attendees need to be able to edit their own fields (same attendee-level permissions as the
review form), with staff-only fields gated appropriately.

Work needed:
- Wire the same `can_edit_fields` / `can_edit(field)` permission logic into the print page
  that `ae_comp__badge_review_form.svelte` already uses.
- The edit panel on the print page should show attendee-editable fields to all authenticated
  users, and staff-only fields to trusted_access+.
- The badge render (v1 or v2) should update live as the attendee edits fields.
- Consider whether the print page needs its own inline edit panel (sidebar or overlay)
  or whether it should share/reuse the review form component alongside the badge render.
- **Do NOT use `email_override` as the send-to address** — always use `event_badge.email`.

### 1. Auto-Scaling Badge Text — In Progress
`ae_comp__badge_obj_view.svelte` using `element_fit_text.svelte` (binary search auto-scale).
Toggle between v1 (heuristic) and v2 (auto-scale) on the print page via the `v1`/`v2` header button.
Heights tuned per layout in `fit_heights` derived object. Still needs visual tuning with real badges.

### 2. QR Code on Badge Front — `ae_comp__badge_obj_view.svelte`
The badge template has a `show_qr` flag (or similar). When toggled on, the QR code should
appear on the front face of the printed badge. Currently QR is only shown on the review form.

- Check the badge template fields for the QR toggle field name (`show_qr`, `qr_enabled`, etc.)
  via `ae_describe event_badge_template` and inspect `ae_comp__badge_obj_view.svelte`.
- The QR code data URL is generated with:
  ```typescript
  qr_data_url = await core_func.js_generate_qr_code('obj', {
      obj_type: 'event_badge',
      obj_id: event_badge_id
  });
  ```
  See `ae_comp__badge_review_form.svelte` for the working pattern.
- Position on badge: typically bottom-right corner of the badge face, sized to fit within
  the template's layout constraints. Do NOT alter structural badge dimensions.
- Must be hidden on `ae_comp__badge_obj_view.svelte` when `show_qr` is falsy.

### 2. Badge Print Controls — UX Improvements (ae_comp__badge_print_controls.svelte)
- Consider: keyboard shortcuts (+ / -) for font sizing while a field is active
- Consider: "Apply to all badges" workflow for font size presets

### 4. Leads Module
Next major work after badge polish. See `documentation/MODULE__AE_Events_Leads.md` (if it
exists) for context. Exhibitor lead scanning via QR code at exhibitor booth → capture attendee
badge data, gated by `allow_tracking` on the badge.

---

## Implementation Status

### ⏳ TASK 4.0: Kiosk Editing — NOT STARTED (updated 2026-03-18)
Print page edit access needs to be opened to attendee-level permissions, not just trusted_access.
The permission model, field list, and `can_edit()` helper from `ae_comp__badge_review_form.svelte`
should be the reference. See Design Intent section above.

**Note (2026-03-18):** `style_href` and `duplex` are both fully implemented and verified in code —
the MODULE doc TODO list was stale. `duplex` is in `properties_to_save`; v2 badge render gates
`show_badge_back` on it. `style_href` loads via `<svelte:head>` in `print/+page.svelte`.

### ✅ TASK 4.1: Auto-Scaling Badge Text v2 — COMPLETE (2026-03-12)
**Files created/updated:**
- `src/lib/elements/action_fit_text.ts` — Svelte action
- `src/lib/elements/element_fit_text.svelte` — Component wrapper
- `src/routes/events/.../ae_comp__badge_obj_view.svelte` — V2 badge render (canonical)
  Debug blocks gated behind `$ae_loc.edit_mode` (hidden in production).
- `print/+page.svelte` — Always uses v2 now. v1/v2 toggle removed. Header redesigned for kiosk UX.
- `ae_comp__badge_print_controls.svelte` — Identity card at top, pronouns moved to attendee section,
  "Staff adjustments" divider before badge_type field.
- `print_list/+page.svelte` — Updated to import v2.
- `ae_comp__badge_obj_view.svelte` (v1) — **Moved to ~/tmp/agents_trash/**

**Kiosk UX improvements (2026-03-12):**
- Print page header: cleaner, shows name + "Ready"/"Printed N×" status chip, event name.
  Header Print Now button removed (duplicate); only Re-print shortcut visible in trusted+edit mode.
- Controls right panel: identity card at top confirms who the badge belongs to before printing.
  Pronouns field is now an attendee-level field (was trusted-only). Staff section labelled.
- Debug JSON blocks in v2 badge render hidden behind global edit_mode flag.

**Print page CSS centering work (also 2026-03-12) — ⚠️ Chromium PDF issue pending:**
Multiple iterations to center the badge on the printed page, working around SvelteKit layout
hierarchy issues. Current approach: `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%)`
on `.event_badge_wrapper`. Key findings:

- `#ae_main_content` has `overflow: auto` — Firefox (spec-compliant) won't let `display: contents`
  dissolve it. Workaround: explicit passthrough block (`display: block; overflow: visible; width: 100%`).
- `app.css` global `overflow: hidden` on `html, body` creates a BFC that collapses body to badge-width.
  Override with `overflow: visible !important` in print CSS.
- **Chrome ignores `@page { size }` for Save as PDF** — verified 2026-03-12. Firefox honors it.
  Chrome uses the system default paper size; Firefox locks to the CSS `@page { size }` value.
  Neither browser lets you change paper size in "Save to PDF" mode — only when printing to a
  physical printer. For actual Epson/Zebra printing, the driver controls paper size; unaffected.
- **Chrome "Default" margins cause the "squish"** — Chrome's Default margin setting inserts
  URL/date/page-number headers and footers into the printable area. These eat into the space
  that `position: fixed; top: 50%` references, making the badge off-center or clipped. Fix:
  set **Margins → None** (or Minimum) in Chrome's print dialog. Content centering verified
  correct: Chrome A4 + None margins produces badge center = 297.5 pts on a 297.5 pt wide page.
  Use Firefox for accurate PDF proofing.
- See `documentation/MODULE__AE_Events_Badge_Templates.md` → "Print Layout Architecture" for full
  technical details.

### ✅ TASK 3: Badge Print Controls Panel — COMPLETE (2026-03-02)

**Files created/modified:**
- `ae_comp__badge_print_controls.svelte` — NEW. Right-edge control panel with per-field
  accordion sections. Font size controls + inline edit forms gated by access level.
- `print/+page.svelte` — layout changed from `flex-row` to fixed right panel.

**Design decisions:**
- Controls panel is `position: fixed right-0 top-20 bottom-0 w-64` — out of normal flow,
  always visible regardless of viewport width. `top-20` (80px) clears the page header.
- Badge area gets `pr-64` to prevent content from hiding under the fixed panel.
  `print:pr-0` and `print:hidden` on the panel restore a clean print layout.
- `bg-white dark:bg-zinc-900` gives the panel a solid background to prevent bleed-through.

**Per-field accordion structure (one open at a time):**

| Field | Access | Font Controls |
| --- | --- | --- |
| Name | Trusted+ edit | ✅ |
| Professional Title | All auth edit | ✅ |
| Affiliations | All auth edit (textarea) | ✅ |
| Location | All auth edit | ✅ |
| Lead Scanning (allow_tracking) | All auth edit | — |
| Pronouns | Trusted+ edit | — |
| Badge Type | Trusted+, only when template has badge_type_list | — |

**Access level note:**
`is_trusted = $derived($ae_loc.trusted_access === true)` — covers Trusted, Administrator,
Manager, Super (cascade). No need to OR in `administrator_access`.

**badge_type_override coupling:**
When badge type is changed via dropdown, both `badge_type_code_override` AND
`badge_type_override` are saved together (name comes from template list). Same behavior in
`ae_comp__badge_obj_view.svelte` and `ae_comp__badge_review_form.svelte`.
Edge case: custom names (e.g. code=`member`, name=`"Life Member"`) must be set manually in DB.

**Font size config (moved from print page to controls component):**

| Field        | Default px | Range    | Step |
|--------------|------------|----------|------|
| Name         | 58px       | 20–80px  | 2px  |
| Title        | 34px       | 14–56px  | 2px  |
| Affiliations | 38px       | 14–60px  | 2px  |
| Location     | 34px       | 14–56px  | 2px  |

Font sizes flow back to the parent via `$bindable()` props so `ae_comp__badge_obj_view`
stays in sync without prop-drilling through a third component.

---

### ✅ TASK 1: Badge Review Form — COMPLETE

The badge review form (`ae_comp__badge_review_form.svelte`) is now fully functional with:
- ✅ All editable fields with access-level gating
- ✅ Print status display section
- ✅ QR code generation and display (hover zoom + click expand)
- ✅ Options and Tickets fields (staff edit / attendee view)
- ✅ Save/Cancel with change detection
- ✅ Override field revert buttons
- ✅ **HTML rendering** for full_name, professional_title, affiliations, location
- ✅ **Accessibility toggle** for text enlargement (text-2xl ↔ text-4xl)
- ✅ **Help modal** with 6 sections of attendee guidance (Flowbite Modal component)
- ✅ Local edit mode (never writes to `$ae_loc.edit_mode`)

**Bug fixed (2026-02-27):** `default_authenticated_fields` and `default_trusted_fields` in
`review/+page.svelte` had incorrect field names causing `can_edit()` to silently drop saves.
Fixed to use exact names matching the form's `can_edit()` checks.

### ✅ TASK 2: Badge Print Font Controls — COMPLETE (v1)

Implemented in commit `3d7279da`. This is a **first draft** — auto font scaling using
mm/inch units is planned as a future iteration.

**What was built:**
- `ae_comp__badge_obj_view.svelte`: 4 new optional props (`font_size_name`, `font_size_title`,
  `font_size_affiliations`, `font_size_location`, all `number` in px). When provided, replaces
  auto inch-based Tailwind class sizing with an inline `font-size: Npx` style. Existing
  auto-sizing behavior is completely unchanged when props are absent.
- `print/+page.svelte`: Screen-only (`print:hidden`) control panel with 4 rows, one per field.
  Each row: label, `[−]` button, value display (`58px` or `Auto`), `[+]` button, `[↺]` reset.
  Step: 2px. `null` state = auto (uses existing inch-based auto-sizing). First `+` click
  activates at a sensible default that approximates the current auto inch values.

**Default px values when first activated (≈ inch equivalents at 96dpi):**

| Field        | Default px | Approx. inch | Range    |
|--------------|------------|--------------|----------|
| Name         | 58px       | ≈ .60in      | 20–80px  |
| Title        | 34px       | ≈ .35in      | 14–56px  |
| Affiliations | 38px       | ≈ .40in      | 14–60px  |
| Location     | 34px       | ≈ .35in      | 14–56px  |

**Future:** Auto font scaling using mm/inch units (physical paper stock measurements).
Will likely need to revisit the inch ↔ mm conversion and potentially expose the auto-sizing
logic as adjustable rather than replacing it with px overrides.

---

## Context

The Events Badges module is mostly complete for navigation and search. Two key pieces of
functional UI were needed before the first show:

1. **Badge Review Form** ✅ — `ae_comp__badge_review_form.svelte` now has complete field
   rendering, edit inputs gated by access level, save/cancel API calls, and display-only
   sections (QR code, print status, option/ticket checkmarks). Also includes accessibility
   features (text enlargement) and help modal for attendee guidance.

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) ✅ IMPLEMENTED

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]
```

**Implemented:** Shows print count with first/last print datetimes. Hidden if `print_count < 1`.
Uses `$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) ✅ IMPLEMENTED

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.

**Implemented using `core_func.js_generate_qr_code()`:**
```typescript
qr_data_url = await core_func.js_generate_qr_code('obj', {
  obj_type: 'event_badge',
  obj_id: event_badge_id
});
```

**Features:**
- Hover: Zoom overlay effect (`qr_hovered` state)
- Click: Expand/collapse that pushes content down (`qr_expanded` state)
- Displays as data URL image from QR code generation
- Reactive: automatically regenerates when `event_badge_id` changes

---

### Section 3: Editable Fields ✅ IMPLEMENTED

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.

**HTML Rendering (implemented 2026-02-27):**
The following fields render HTML markup using `{@html}` when viewing (not when editing):
- `full_name_override` / `full_name`
- `professional_title_override` / `professional_title`
- `affiliations_override` / `affiliations`
- `location_override` / `location`

This allows for rich text formatting (bold, italic, line breaks, etc.) in badge displays.

**Accessibility Features (implemented 2026-02-27):**
- Text enlargement toggle button in sticky header
- Normal size: `text-2xl` on field values
- Enlarged size: `text-4xl` on field values
- Button shows visual feedback (gray → blue, "Larger" → "Normal" label)
- Applied consistently across all text fields

**Help Modal (implemented 2026-02-27):**
- Flowbite Modal component with 6 sections
- Sections: Reviewing Badge, Editing Info, Accessibility, QR Code, Lead Scanning, Assistance
- Triggered by Help button (BadgeQuestionMark icon) in sticky header
- Currently has placeholder text — can be customized per event/client

#### 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`; **renders HTML** when viewing |
| `professional_title_override` | text input | Fallback display: `professional_title`; **renders HTML** when viewing |
| `affiliations_override` | textarea | Fallback display: `affiliations`; **renders HTML** when viewing |
| `phone_override` | text input (tel) | Fallback display: `phone` |
| `location_override` | text input | Fallback display: `location`; **renders HTML** when viewing |
| `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`) ✅ IMPLEMENTED

| 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 |

> **Edge case — custom badge type name:** If an attendee needs a standard badge type code (for
> CSS styling) but a slightly different displayed name (e.g. code=`member`, name=`"Life Member"`),
> set `badge_type_override` directly in the DB. Do **not** use the dropdown — selecting from the
> dropdown in the UI overwrites `badge_type_override` with the standard name from the template
> list. This is an intentional trade-off: coded for the normal case (dropdown keeps both fields in
> sync), special cases handled manually by Scott in the DB.
| `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`) ✅ IMPLEMENTED

**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) ✅ IMPLEMENTED

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) ✅ IMPLEMENTED

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 = `$ae_loc.trusted_access`.
`trusted_access` is `true` for Trusted and every level above it (Administrator, Manager, Super)
— no need to OR in `administrator_access` since it's already implied by the cascade.

---

## 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`
