Files
OSIT-AE-App-Svelte/documentation/PROJECT__AE_Events_Badges_Review_Print.md
Scott Idem c4e85b1fe3 feat(badges): print/review pages, 4-button list, Lucide icons, permissions doc
Badge search results list (ae_comp__badge_obj_li):
- 4 action buttons per row: Print, Review (nav link), Copy Link (clipboard), Email Link
- Visibility rules: unprinted-only for non-edit mode; all non-hidden for trusted+edit
- Plain name display (User/EyeOff icon) — name is no longer a print link
- Obscured email for non-trusted users
- Debug row (ID, CR, UP, PC, FP, LP) in edit mode
- All icons converted to Lucide (Font Awesome removed)

Badge print page (/print):
- 3 header action buttons: Print Now, Review (nav), Email Link
- Removed old [badge_id]/+page.svelte placeholder (moved to trash)
- Added is_trusted, is_edit_mode, print state derived vars
- "Already printed Nx — last [timestamp]" warning inline with name
- Removed unused imports (browser, onMount, events_slct)

Badge review page (/review):
- 3 header action buttons: Print (nav), Copy Link (clipboard), Email Link
- Added events_loc for email placeholder + title event name
- Added is_edit_mode, print_count, is_printed, copy_status
- FA icons replaced with Lucide (ShieldCheck, UserCheck, User)
- Title now includes event name (was missing)

Infrastructure:
- print/+page.ts and review/+page.ts added (non-blocking badge loaders)
- ae_comp__badge_review_form.svelte stub created (fields pending)
- Fixed: components no longer write to $ae_loc.edit_mode (critical bug)

Docs:
- NEW: AE__Permissions_and_Security.md — full permissions hierarchy reference
- NEW: PROJECT__AE_Events_Badges_Review_Print.md — agent task brief for review form + print font controls
- UPDATED: MODULE__AE_Events_Badges.md rev 5 — field permissions spec, header buttons, still-needed list by priority

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:12:22 -05:00

13 KiB
Raw Blame History

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

Created: 2026-02-27 Branch: ae_app_3x_llm Priority: HIGH — first live event is Axonius, NYC, mid-April 2026 Owner: Scott Idem / One Sky IT


Context

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

  1. Badge Review Formae_comp__badge_review_form.svelte is currently a stub. It needs actual field rendering, edit inputs gated by access level, save/cancel API calls, and display-only sections (QR code, print status, option/ticket checkmarks).

  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)

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]

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

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.

Check the legacy AE Badge version for existing QR generation code. Look in:

  • src/lib/ae_events/ for any QR-related utilities
  • ae_comp__badge_obj_view.svelte — the badge render component almost certainly generates a QR code already for the printed badge. Reuse that logic/component if possible.

The QR code value should encode the badge ID or a URL that resolves to the badge.


Section 3: Editable Fields

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.

Field Input Type Notes
pronouns_override text input Fallback display: pronouns
full_name_override text input Fallback display: full_name
professional_title_override text input Fallback display: professional_title
affiliations_override textarea Fallback display: affiliations
phone_override text input (tel) Fallback display: phone
location_override text input Fallback display: location
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)

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

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)

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)

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 = administrator_access || trusted_access.


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