Root cause: flex-row flex-wrap on the session header caused the datetime
and name to compete for the same row. Long session names (up to 300 chars)
wrapped onto 2-3 lines while short names stayed 1 line, making the header
jump in height every time the operator switched sessions.
Fix:
- header: flex-row flex-wrap -> flex-col; datetime and name are now
always on separate rows, header height is predictable in both cases
- h2 name: shrink -> grow line-clamp-2 min-w-0; height is always exactly
2 lines, never less, never more; full text accessible via title attribute
- code badge: added shrink-0 so it is never squeezed by a long name
- removed justify-between/justify-end conditional classes (no longer relevant)
- Section 508: title attribute on h2 provides full text for screen readers
- Remove dead .field_editing_wrapper CSS rules from element_ae_crud_v2.svelte
(template migrated to field_editing_wrapper_v2 with Tailwind)
- Fix TS error: use optional chaining on person_obj key in ae_comp__person_obj_tbl.svelte
- Fix state_referenced_locally: wrap data.user init with untrack() in users/[user_id]/+page.svelte
- Replace misused <label> with <span> for visual section headings (a11y) in:
launcher_cfg_native_os.svelte, launcher_cfg_health.svelte,
launcher_cfg_local_actions.svelte, launcher_cfg_template.svelte
Fixed all 27 remaining instances across 19 files. Keys used:
- Object ID fields where available (e.g. account_id_random, event_file_id)
- index for logger lists with no reliable unique key
- Property name for Object.entries() loops
Miscellaneous small changes to events (badges, launcher, leads, pres_mgmt,
settings), journals, reusable elements (crud, field editor), app components,
core components, and test README. Mostly 1-2 line changes per file.
- ae_idaa_stores.ts: update default novi_admin_li UUID; add staff UUID to
novi_trusted_li hardcoded defaults.
- +layout.svelte (idaa): only overwrite admin/trusted lists from site_cfg_json
when the list is non-empty, so hardcoded defaults are never silently cleared.
Remove $ae_loc.iframe requirement for 'authenticated' access level — the
presence of a valid Novi UUID in the URL is sufficient proof regardless of
whether the iframe flag is set yet.
- app.css: add @custom-variant dark so Tailwind v4 respects .dark class
on <html> instead of always following OS prefers-color-scheme.
- app.html: remove hardcoded class="light" (now set dynamically).
- +layout.svelte: toggle .dark/.light on <html> when ae_loc.theme_mode changes.
- e_app_theme.svelte: related theme toggle changes.
Fixed three issues in ae_comp__badge_obj_view.svelte:
1. Bug: {#await} block used src={qr_data_url} (the Promise) instead of src={result}
(the resolved data URL) — QR image never displayed correctly on badge front.
2. Layout: .special div had no flex context; added flex-row + items-end so the QR
sits at the bottom-right of the body section via ml-auto.
3. Cleanup: removed dead second QR block that awaited event_badge_qr_id_get_promise
(always null), which could never render.
Also marks CRUD v2 refactor as complete in TODO__Agents.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously buttons were opacity-0 until hover — invisible even in edit mode.
Changed to opacity-20 base so users can see which fields are editable, opacity-100 on hover.
Matches the behavior in element_data_store_v3.svelte.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix layout shift on edit_mode toggle: always render the edit button
(using invisible/pointer-events-none) so the flex container doesn't
reflow when edit_mode is toggled on/off.
- Fix 'store.set is not a function' crash: remove $bindable() from
current_value. The component is SWR-first; after a successful PATCH
liveQuery updates the prop from Dexie. Trying to write back to a
readonly liveQuery-derived prop caused the crash.
- Fix stale display after save: add has_optimistic flag + display_value
derived. After a successful PATCH, display_value shows draft_value
immediately without waiting for liveQuery. Cleared automatically when
current_value catches up, or on cancel/re-open of edit mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. launcher_presentation_view: accept session_type prop from parent
instead of relying on event_session_type_code on file objects (which
is not reliably populated in Dexie). Use session_type to correctly
set hide_meta, session_type, and open_method on the file container.
2. launcher_session_view: pass session_type={type_code} to
Launcher_presentation_view; restore Launcher_presenter_view_posters
in the presenter list for poster mode (hide_name=true). Some events
store poster files at the PRESENTER level (for_id=event_presenter_id)
— particularly group/company presenters — so both paths must render.
3. launcher/+layout.svelte: fix poster modal image 403. The img src was
using the event_file download endpoint which requires auth headers a
plain img tag cannot send. Switched to the hosted_file endpoint with
key=account_id, which is browser-compatible. Also guarded on
modal__event_file_obj.hosted_file_id for safer access.
4. launcher_cfg_screen_saver: rename section title to 'Poster Screen Saver'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two issues in poster session view:
1. 'No files for this presenter' message always showed because
file_count on the presenter is 0 — poster files are attached to
the presentation, not the presenter. Removed the misleading message
from launcher_presenter_view_posters.svelte with an explanation.
2. launcher_presentation_view.svelte was not passing hide_meta, so
the OS toggle, date badge, and file size always rendered below the
poster button. Added hide_meta={true} for poster files to match the
clean display already used in launcher_presenter_view_posters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
native_device can be undefined during initial load (before device config
is fetched). The select bind:value was doing a hard property access that
threw a TypeError crash when native_device was null/undefined. Wrapped
in {#if $ae_loc.native_device} to defer rendering until it's available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The $effect in launcher_presentation_view.svelte was calling
load_ae_obj_id__event_presentation() on every prop update. The SWR
pattern in that function always fires a background Dexie write, which
triggered the upstream liveQuery, which updated the prop, which
re-ran the $effect — creating an infinite loop that saturated the API
and crashed the browser tab within 30-60 seconds.
Fix: guard on last_loaded_id so the API call only fires when the
presentation ID actually changes, not on every downstream re-render.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SWR pattern always fires a background API call on cache hits, so the
15s session interval created a continuous API stream even when data was
fresh. Increased all defaults: session 15s->60s, location/device 30s->60s,
presentation/presenter 45-60s->120s.
Added sync_paused guard to all six refresh functions and a Pause/Resume
toggle in the Sync Timers config section (visible without edit mode) so
testing and troubleshooting don't require a full reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
type_code was a prop in launcher_session_view that was never passed by
the parent layout, silently killing all poster-specific code paths
(sort order, section labels, poster presenter component, inline
presenter display). Fixed by deriving type_code directly from the
session LiveQuery object already present in the component.
Also adds a poster icon badge to the session list so poster sessions
are visually distinct from oral sessions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Disabled items are treated as functionally deleted for all end clients
(including Trusted Access staff). Only Manager + Edit Mode should see
Show/Hide Disabled controls — previously using administrator_access and
missing edit_mode gate in BB and archives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add keyed {#each} to recovery meeting list and exhibit tracking list
to satisfy Svelte's each-block-key lint rule and ensure correct DOM
reconciliation on list updates
- Gate Zoom/Jitsi copy-to-clipboard buttons behind manager_access in
both the recovery meeting list view and single meeting detail view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- e_app_access_type: reset checked_passcode on clear so same passcode
can be re-entered without a page refresh (guard was blocking re-entry)
- element_data_store_v3: wire display prop to wrapper CSS style;
gate "not found" diagnostic to administrator+ or trusted+edit_mode;
public/anonymous visitors no longer see missing block warnings
- +page.svelte: add manager_access exception to header/content class_li
so managers can see "not found" diagnostics (matches footer pattern)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migrate event_exhibit and event_exhibit_tracking CRUD to V3 API (parent_type/child_type params).
- Implement Element_qr_scanner_v3.svelte: A Svelte 5 / Runes component using html5-qrcode with auto-start and unique viewfinder IDs.
- Integrate QR Scanner v3 into ae_comp__badge_search.svelte and lead capture.
- Refactor Exhibitor Leads UI:
- Add 'Rapid Scan' vs 'Qualify Mode' toggles for efficient lead capture.
- Upgrade ae_comp__lead_detail_form.svelte to support new question/response schema with backward compatibility.
- Implement 'Sign Out of Booth' functionality in exhibit management.
- Optimize lead detail layout for mobile readability and high information density.
- Fix component prop sync for event_id and exhibit_id.
- UI/UX refinements: standardizing icons (SquarePen), cleaning up unused imports, and improving responsive states.
- 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.
Two compiled layout CSS files in src/lib/ae_events/badges/css/:
- badge_layout_epson_4x5_fanfold.css — 4"×5" per side, Epson ColorWorks C3500
fanfold duplex; preferred for general conference use (ISHLT, demos)
- badge_layout_zebra_zc10l_pvc.css — 3.5"×5.5" PVC card, Zebra ZC10L,
single-sided (pair with duplex=0 on template)
Rules scoped under [data-layout="..."] on the wrapper — beats Tailwind utility
class specificity without !important. Vite hot-reloads these in dev.
Badge component: add data-layout attribute from template.layout field; import
both layout CSS files (falls back to Tailwind 4"×6" defaults if layout unset).
Print page svelte:head: inject @page paper-size rule per layout code
(@page cannot use attribute selectors so it must be dynamic). Also wire
style_href as a <link> for per-event external client branding CSS.
db_events.ts Badge_template interface: add style_href and duplex fields.
MODULE doc: update layout codes table with badge_4x5_fanfold entry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add duplex + style_href to properties_to_save so they're cached in IDB
- Derive show_badge_back: true when duplex is null/unset (safe default for
existing templates) or 1; false when duplex=0 (single-sided, e.g. Axonius
NYC using Zebra ZC10L PVC cards)
- Wrap entire badge_back section in {#if show_badge_back} so the DOM node
is fully removed rather than just hidden — cleaner than a CSS class approach
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static `badge_type_code_li` array (hardcoded ISHLT 2024 types) with
`$derived.by()` that parses `$lq__event_badge_template_obj.badge_type_list` JSON.
Each event's template defines its own badge type set — the component now reflects
that correctly. Falls back to `[]` on missing/invalid JSON (hides the type select).
Also add MODULE__AE_Events_Badge_Templates.md documenting template field reference,
external CSS approach, layout codes, duplex field plan, and standard template setup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ae_comp__badge_review_form.svelte:
- Full implementation of the badge review form (Task 1)
- Editable fields gated by access level (attendee / trusted / admin)
- Save/cancel with change detection, override revert buttons
- QR code display (hover zoom + click expand)
- Print status section, options/tickets, T&C block
- HTML rendering for name/title/affiliations/location fields
- Accessibility font-size toggle (text-2xl ↔ text-4xl)
- Help modal (Flowbite) with 6 sections
- Local edit mode — never writes to $ae_loc.edit_mode
ae_events__event_badge.ts:
- Add missing fields to properties_to_save so they are persisted to IDB:
pronouns_override, phone, phone_override, registration_type(_code/_override),
allow_tracking, agree_to_tc, other_1-8_code, ticket_1-8_code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add optional font_size_{name,title,affiliations,location} px props to
ae_comp__badge_obj_view; when set, replaces auto inch-based sizing with
an inline style so the caller controls the value
- Add screen-only (print:hidden) font size control panel to print page:
four [−] [value] [+] [↺] rows, step 2px, null = auto (default)
First + click activates at a sensible default that approximates the
auto-sized inch values; ↺ button resets back to auto mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
default_authenticated_fields was missing: pronouns_override, phone_override,
allow_tracking, agree_to_tc — so attendees couldn't edit those fields even though
the form rendered inputs for them.
default_trusted_fields had 'email' and 'badge_type_code' instead of 'email_override'
and 'badge_type_code_override', causing the can_edit() check in handle_save() to
silently drop those changes. Also added the full trusted field set: registration_type,
other_1-8_code, ticket_1-8_code, hide, priority, notes.
Also removed unused lucide imports (ShieldCheck, User, UserCheck) left over from
the removed access level indicator block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- Add data-testid to badge edit/save/cancel/print buttons and professional title input
- Fix API mock routes (badge GET uses /v3/crud/event_badge/{id}, not nested)
- Add badge_template mock so badge view renders
- Add event_badge_id_random and id_random fields to all mock responses
- Split workflow test into its own test() instead of being inline in beforeEach
- Use data-testid selectors throughout for stability
- Split search query by spaces
- Apply AND logic: all words must match
- Single word: LIKE '%word%'
- Multi-word: LIKE '%word1%' AND LIKE '%word2%'
Example: 'scott idem' now searches for both 'scott' AND 'idem'
Previously searched for literal 'scott idem' phrase which failed.
Fixes search bug discovered during Playwright test development.
CRITICAL FIX (same pattern as event_file fix):
- search__event_badge(): Now returns processed_obj_li instead of unprocessed result_li
- load_ae_obj_id__event_badge(): Returns processed object after IDB save
- load_ae_obj_li__event_badge(): Returns processed list after IDB save
- create_ae_obj__event_badge(): Returns processed object after IDB save
- update_ae_obj__event_badge(): Returns processed object after IDB save
Pattern: All functions now return what was actually saved to IDB (processed data)
This ensures consistency between API return values and IDB cached data.
TEST IMPROVEMENTS:
- Add full_name field to badge mock data (not just full_name_override)
- Ensures mock data matches real API structure
- Fix session detail page params not being passed to component (causing undefined session_id)
- Fix wrapper components discarding API enriched data by re-fetching from IDB
- Fix event_file, event_session, event_presenter wrappers to preserve API data
- Add optional chaining for hash_sha256 field to prevent undefined crashes
- Fix search__event_file to always process API results before returning
- Ensures hosted_file_size -> file_size field mapping for reports
- All pres mgmt reports (files, sessions, presenters) now work on cold-start
Added await Promise.resolve() yields after IndexedDB writes to ensure
Dexie observers fire before function returns. Aligns with pattern
established in event loaders fix.
Journals module was already working correctly (preserved try_cache),
but adding yields ensures consistent timing behavior across all
nested data loading patterns.