# PROJECT: AE Events Badges — Review Form & Print Font Controls
**Created:** 2026-02-27
**Last Updated:** 2026-03-12
**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 NEXT UP
---
## 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` / `ae_comp__badge_obj_view_v2.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 (v2) — In Progress
`ae_comp__badge_obj_view_v2.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 (2026-03-12)
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.
### ✅ 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_v2.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/gemini_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 `` /
`