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>
13 KiB
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:
-
Badge Review Form —
ae_comp__badge_review_form.svelteis 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). -
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)
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 utilitiesae_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.
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 |
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.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