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