Files
OSIT-AE-App-Svelte/documentation/PROJECT__AE_Events_Badges_Review_Print.md
Scott Idem 5c3823f41a feat(badges): implement badge print controls panel and refine badge overrides
- Create ae_comp__badge_print_controls.svelte: A fixed-right-edge panel for per-field accordion controls, font size adjustments, and inline editing.
- Refactor print/+page.svelte to integrate the new controls panel and standardize font size state management via $bindable() props.
- Update ae_comp__badge_obj_view.svelte and ae_comp__badge_review_form.svelte to correctly sync badge_type_code_override and badge_type_override.
- Improve badge_type_name derivation logic to prioritize staff overrides.
- Hide unused receipt/ticket sections in badge view pending future redesign.
- Update documentation (PROJECT and TODO) to reflect completion of Task 3.
2026-03-02 19:47:11 -05:00

22 KiB
Raw Blame History

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:
    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 2080px 2px
Title 34px 1456px 2px
Affiliations 38px 1460px 2px
Location 34px 1456px 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 2080px
Title 34px ≈ .35in 1456px
Affiliations 38px ≈ .40in 1460px
Location 34px ≈ .35in 1456px

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:

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:

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

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():

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

// 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., 8px24px for name, 7px18px 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:

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

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

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