- PROJECT doc: Task 2 status → complete (v1); added implementation
details, default px values table, and note on future mm/inch iteration
- PROJECT doc: noted the review page field list bug fix (commit 011fc19a)
- MODULE doc: added "Recently Completed" section above Still Needed;
cleared Badge Review Form and Print Font Controls from HIGH PRIORITY list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 KiB
PROJECT: AE Events Badges — Review Form & Print Font Controls
Created: 2026-02-27
Last Updated: 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
Status: ✅ TASK 1 (Badge Review Form) COMPLETE | ✅ TASK 2 (Print Font Controls) COMPLETE — v1
Implementation Status (2026-02-27)
✅ 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, allnumberin px). When provided, replaces auto inch-based Tailwind class sizing with an inlinefont-size: Npxstyle. 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 (58pxorAuto),[+]button,[↺]reset. Step: 2px.nullstate = 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:
-
Badge Review Form ✅ —
ae_comp__badge_review_form.sveltenow 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. -
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 theae_comp__badge_obj_view.svelterender — not the page layout/template structural dimensions.
Read documentation/MODULE__AE_Events_Badges.md for full module context before starting.
MANDATORY: Before You Start
-
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 toproperties_to_saveinsrc/lib/ae_events/ae_events__event_badge.tsif they are not already saved to IDB. -
Fields to specifically confirm exist in
event_badgeschema:pronouns,pronouns_overridephone,phone_overrideallow_trackingagree_to_tcother_1_codethroughother_8_code(the "option" fields)ticket_1_codethroughticket_8_coderegistration_type,registration_type_coderegistration_type_override,registration_type_code_override
-
Run
npx svelte-checkbefore committing. Baseline is 77 errors (all pre-existing, none in the badge module files). Do not introduce new errors. -
Do NOT write to
$ae_loc.edit_modefrom any badge component. This was a critical bug (fixed 2026-02-27). Seedocumentation/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_objvalues) - Show save/cancel buttons only when something has changed (
has_changesderived) - Show a success/error state briefly after save (1-2 seconds, then reset)
- Cancel resets local state back to
$lq__event_badge_objvalues - Use
data-testid="badge-review-save-btn"anddata-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_hoveredstate) - Click: Expand/collapse that pushes content down (
qr_expandedstate) - Displays as data URL image from QR code generation
- Reactive: automatically regenerates when
event_badge_idchanges
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_nameprofessional_title_override/professional_titleaffiliations_override/affiliationslocation_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-2xlon field values - Enlarged size:
text-4xlon 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
Attendee-Editable Fields (shown to all access levels with link)
| 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 |
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.sveltecurrently uses) - Min/max per field (e.g., 8px–24px for name, 7px–18px 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-checkbefore every commit
What NOT to Do
- Do NOT touch
@pageCSS or badge template structural dimensions — print layout is out of scope - Do NOT write to
$ae_loc.edit_modefrom any component - Do NOT connect
mod_badges_json.edit_permissionsyet — hardcoded field lists are intentional for now - Do NOT implement the email API —
send_review_email()placeholder stays asalert() - Do NOT add
person_passcodeDB 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-btnbadge-review-cancel-btnbadge-review-full-name-inputbadge-review-agree-to-tc-checkbox