# PROJECT: AE Events Badges — Review Form & Print Font Controls **Created:** 2026-02-27 **Last Updated:** 2026-03-02 **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 --- ## Next Up for Badges (TASK 4) ### 1. 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) - Scott has identified areas for improvement — TBD next session - Consider: keyboard shortcuts (+ / -) for font sizing while a field is active - Consider: "Apply to all badges" workflow for font size presets ### 3. 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. --- ## Implementation Status ### ✅ 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 `` / `