When navigator.onLine is false or the account is ghost (API unreachable),
bypass the 20-second API timeout entirely and fire window.print() at once.
The existing error state ("Printed — count NOT saved") already covers this
case. Staff can correct the print count manually after connectivity returns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the static full-width offline banner with a two-state toggle:
expanded banner (default) with a collapse button, and a small chip in
the top-right corner when collapsed. Adds a subtitle line explaining
what still works offline vs. what requires network. Changes "No API"
chip label to "API Error" and "Offline" title to "Device Offline".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Badges without person_passcode are now viewable by anyone with the URL —
open access is granted on badge load. Previously this was explicitly
denied. The passcode entry form is only shown when the badge actually
has a passcode configured. Auto-validate effect expanded to cover the
no-passcode case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the single alert() call with an inline confirmation row per badge.
Clicking "Email Link" shows "Send / Cancel" in place so accidental sends
are avoided. Only one badge can be in confirm state at a time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces filter:hue-rotate() with CSS @property --ph-hue/--ph-lit animated
as numbers, applied as oklch() colors on SVG rect/line children. OKLCH keeps
perceptual lightness constant across hue rotation — no more brown/dark-blue
variance between slots. Pulse mode animates --ph-lit 0.42→0.78 for breathing.
Adds slow_pulse cfg_json flag + form checkbox. @property inherits:true lets
the animated value cascade from the div to its SVG children without per-child
animation declarations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds punch_holes.slow_pulse cfg_json flag. When enabled, replaces the fast
2.5s linear hue-rotate with a 6s ease-in-out breathing animation that dims
(0.55 brightness) to bright (1.30) while shifting 180° of hue and back.
Same 120° phase offsets apply (2s apart). Form shows a Slow Pulse checkbox
below the slot cards whenever at least one slot has rainbow enabled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each slot gets a fixed animation-delay (left: 0s, right: -0.833s, center: -1.667s)
so they are 120° apart in the hue cycle — same speed, different start points.
Replaces the shared-wrapper approach (all same phase) with per-slot CSS classes
that encode the phase offset, giving a proper tri-phase RGB cycling effect.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each rainbow-enabled slot now has a companion print-only div with an inline
SVG linearGradient (R→Y→G→C→B→M spectrum). On screen: the animated
hue-rotate div cycles. On print: CSS hides the animated div and shows the
static gradient instead. X lines print in semi-transparent black over the
gradient fill for visibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds left/right/center_rainbow to punch_holes cfg_json. When enabled,
applies a CSS hue-rotate animation (2.5s loop) to the marker div using
a saturated red base color so the full visible spectrum appears. Template
form shows a Rainbow checkbox per slot; hides color pickers when active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds header_padding_top/right/left alongside existing header_padding_bottom.
Removes hardcoded p-2 class — all four sides now set via inline style with
0.5rem default. Template form shows a 2×2 padding grid in Header & Branding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds left_fg/left_bg, right_fg/right_bg, center_fg/center_bg to punch_holes
cfg_json, plus shared fg/bg fallback. Template form shows color pickers per
slot (only when slot is enabled). Defaults: #777777 stroke, rgba white fill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
z-index: 10 ensures markers always render above the header image regardless
of DOM order. Inset 1mm on all sides from physical hole boundary to account
for printer registration variance (3mm-tall slot has no margin for error).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds cfg_json.punch_holes.{left,right,center} to mark pre-perforated badge
clip slots with X overlays. Slots are 5/8in x 1/8in, 1/4in from top,
3/8in from left/right edges. Markers print on the badge so attendees know
where to push out the perforations. Template form exposes checkboxes in
Header & Branding. Documented in MODULE__AE_Events_Badge_Templates.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces hardcoded border-bottom/padding-bottom on badge_header div with
cfg_json fields: header_border_color, header_border_width, header_padding_bottom.
Empty color = no border. Template form exposes all three in Header & Branding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows a Templates button (manager+ edit mode only) before Create Badge,
linking directly to the badge templates management page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds cfg_json.header_margin_top to BadgeTemplateCfg. Badge view replaces
hardcoded mt-8 (2rem) with this value; falls back to 2rem when unset.
Template form exposes the field in the Header & Branding section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace show_hidden checkbox with visibility_filter select (Default /
Show Hidden / Show Disabled+Hidden) — collapses two orphaned boolean
fields (show_hidden, show_not_enabled) into one purpose-built value;
wires disabled-badge filter through to both IDB and API paths
- Add max-results stepper (edit mode only): steps of 25 up to 250,
steps of 100 up to 2550; tier-capped (trusted=250, manager=2550);
stepper uses pure reactivity — no handle_search_trigger() call needed
- Fix fallback liveQuery (SCENARIO 2): was hardcoded .limit(50);
now reads qry_result_limit in outer $derived.by so Svelte tracks it
and stepper updates the no-text browse list immediately
- Fix Search button disabled state: replace pointer-events-none +
class:opacity-50 with HTML disabled attribute + disabled:cursor-not-allowed
so hover cursor reflects disabled state correctly
- Global placeholder fix (app.css): add italic + opacity-0.6 rule for
.input/.textarea ::placeholder in light mode; add italic to dark rule —
prevents placeholder text from reading as typed content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
667 font files (~134MB) were being passed to cache.addAll() on every SW
install. cache.addAll() is atomic — a single failed request aborts the
entire install. Browser Cache Storage quota is typically 50-100MB on
mobile, so the SW has likely been silently failing to install on most
mobile devices, negating all caching and the skipWaiting fix.
Only 3 font files are referenced by the browser (app.css). The rest are
badge-print fonts for server/Electron use and do not need to be precached.
The browser's normal HTTP cache handles them when the print page is visited.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BOOTSTRAP__AI_Agent_Quickstart.md:
- Mistake #15 addendum: events/session modules use legacy tmp_sort_1 encoding
(priority=true→'1'), not build_tmp_sort — requires descending sort until migrated
- Mistake #16 (new): service worker without skipWaiting+clients.claim silently serves
stale code to long-lived tabs; explains the "can't reproduce in dev" pattern that
likely caused the IDAA recovery meetings issue for months
CLIENT__IDAA_and_customized_mods.md:
- New "Sort Encoding" section: table of legacy vs build_tmp_sort modules with
correct comparator direction for each
- New "Search Trigger" section: explains why $slct.account_id not $ae_loc.account_id
TODO__Agents.md:
- IDB Sort: added ae_events__event migration task + legacy encoding warning
- DevOps: marked service worker fix complete; replaced nginx caching item with
proxy buffer tuning task
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without these two calls, a new service worker installs in the background but
sits in 'waiting' state until every tab running the old version is closed.
Users who leave idaa.org open all day (common for IDAA members in the iframe)
would run the old JS bundle for hours or days after a fix is deployed, with no
indication that an update exists.
skipWaiting() — new SW activates immediately after install instead of waiting
clients.claim() — new SW takes control of all open tabs right away
This is the most likely root cause of "can't reproduce in testing but users
keep seeing the error": developers refresh/close tabs constantly, end users
don't. The old broken code kept running in their long-lived browser sessions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three fixes for the IDAA Recovery Meetings load/display issues:
1. Sort direction: events use legacy encoding (priority ? 1 : 0), not build_tmp_sort.
priority=true→'1' requires DESCENDING sort to put priority items first. The prior
commit (ee79e33a2) incorrectly applied the build_tmp_sort-compatible ASC comparator
to the events module, which does not use that encoding. Reverted in +page.svelte
(both fast-path and API-results sort) and ae_idaa_comp__event_obj_li_wrapper.svelte.
2. Stale account_id gate: search $effect and handle_search_refresh now read
$slct.account_id (set only by the bootstrap Sync Effect, reliable) instead of
$ae_loc.account_id (persisted localStorage, may be stale from a prior session).
Follows the mistake #14 pattern from BOOTSTRAP__AI_Agent_Quickstart.md.
3. Clear Cache & Reload: now enumerates and deletes ALL IDB databases via
indexedDB.databases() (not just db_events.event), clears all localStorage and
sessionStorage (not just two keys), and preserves the iframe reload URL for
Novi re-authentication — matching the Full Reset pattern in e_app_help_tech.svelte.
4. IDB version bump: events.event → v3 (precautionary; flushes any stale cached
event records on next user load in case prior deploys missed a bump).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Controls column: max-w-sm (384px) prevents flex-1 from consuming all
remaining horizontal space on wide screens
- Row wrapper: justify-center + items-center centers the badge+controls
pair in the available width (equal margins on both sides)
- Mobile: full-width stacked layout unchanged; badge centered via items-center
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Print page: controls panel moves from position:fixed right overlay to an
in-flow flex column next to the badge. Sticky on lg+ screens; stacks below
badge on tablet/phone. Eliminates pr-64/pr-80 padding hacks on header and
badge area. Print behavior unchanged — .event_badge_wrapper uses position:fixed
relative to @page, so the surrounding layout structure is irrelevant.
- Remove is_editing (was only used to toggle fixed panel width; no longer needed).
- Debug JSON block removed from ae_comp__badge_obj_view (visual render component
should not contain admin tooling) and moved to controls Staff section.
- Debug JSON uses whitespace-pre + overflow-x-auto so long lines scroll
horizontally instead of wrapping into hundreds of short lines. Collapsible
via native <details>/<summary> with no JS state required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add pr-64/pr-80 + transition to page <header> so Review/Email actions
stay clear of the fixed right controls panel (same as badge_render_area)
- Remove redundant Re-print button from header — controls panel Test button
covers the same use case; having both caused clipping and confusion
- Fix datetime formatting: toLocaleString → ae_util.iso_datetime_formatter
(date_full_no_year + time_12_long) for consistent display with badge list
- Unify loading state to p-16/text-xl/mb-2 matching badge list style
- Polish Badge Not Found layout; swap button weights (Back = primary action)
- Add left-edge shadow to controls panel for better visual separation
- Badge view debug section: gate to administrator_access, add object ID
labels, add hover:max-h-64 expand and transition on pre blocks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a compact print info row below each printed badge for trusted users
(not in edit mode — debug row already covers that):
Printed 2× · First: June 9 9:14 AM · Last: June 9 11:32 AM
Gives staff quick at-a-glance confirmation that a badge was printed and when,
without needing to enter edit mode.
Also fixes a logic bug in the attendee "Checked in" card: the "last print"
line was comparing == (same datetime) instead of !== (different datetime),
so it only appeared when first = last (single print) — backwards. Fixed to
show only when multiple prints have occurred.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pressing the Search button or Enter with fewer than the required min chars
now focuses the input field instead of silently doing nothing, so the user
gets clear feedback about where to type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was: "Enter your name above to find your badge."
Now: "Search by name, email, or organization above to find your badge."
Mirrors the actual fast-path fields (given/family name, email, default_qry_str)
and the search input placeholder text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The +page.svelte {#if search_status=loading && ids.length===0} gate is now
redundant — ae_comp__badge_obj_li handles all states internally (Searching...,
Enter your name, No results). Removing it also fixes a UX regression where
trusted-user IDB fallback results would be hidden by the gate whenever a new
API search fired. Component is now always mounted and manages its own state.
Also replaces deprecated BarChart2 with ChartColumnBig (lucide rename).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
transition:fade on the initial spinner caused Svelte to keep the outgoing
element in the DOM for the full 200ms outro while the incoming badge list
was already rendered — both were live simultaneously, colliding on height
and producing the visible bounce. Initial cold-start load doesn't need a
transition; instant swap is fine.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scrollbar shift:
- Add [scrollbar-gutter:stable] to #ae_main_content in events layout so a
scrollbar appearing on first results load no longer reflows the centered
search form (was shifting ~8px left on Linux)
Empty/loading state consistency:
- Move search_status prop into ae_comp__badge_obj_li so it can swap its own
empty state: spinner + "Searching..." while a search is in progress,
UserSearch icon + prompt text otherwise
- Unify p-16 / size-3em / mb-2 / text-xl across all three states (initial
load, searching, no results) so height never jumps between transitions
- Pass search_status from +page.svelte to the component
Transitions:
- transition:fade on initial-load spinner div
- transition:slide on Create/Upload badge button row (appears with edit mode)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adds fade/slide transitions throughout the search form: form mount/unmount,
filter row, QR scan button, QR scanner panel, Show Hidden, Remote First labels
- Min-chars hint switches from class:invisible to opacity-0/opacity-50 +
transition-opacity so it fades instead of snapping
- Clear button switches from class:hidden to opacity-0 + pointer-events-none
+ transition-all so it fades without causing layout shifts
- "Start Here" button gets transition-opacity for smooth dim on first keystroke
- Replaces FileSearch with UserSearch icon in the empty state
- Adds w-full to empty state div to prevent subtle page-width shift between
no-results and results states
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Public (attendee) kiosk: unprinted badges link to /review; printed
badges show green "Checked in · Nx · First/Last" row (non-clickable)
- Public attendees no longer see Print button (staff-only action)
- Printed badges sort to end of list for public non-trusted users
- Manager access: Print (reprint) and Email Link buttons always visible
without requiring Edit Mode; main row behavior unchanged
- Empty state wording: context-aware — "Enter your name above to find
your badge" for public users with no query vs "No badges found" after
an actual search
- Docs: Epson C3500 fanfold section filled in (was empty placeholder);
style_href/duplex implementation status corrected in badge templates
doc; Axonius C3500 layout TODO marked complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
build_tmp_sort() encodes priority=true as '0' for ascending sort. JS comparators
were using b.localeCompare(a) (descending), inverting the encoding so priority=false
items sorted first. Fixed to a.localeCompare(b) in ae_journals_search_helpers.ts (3
sites in recovery_meetings +page.svelte and wrapper component).
Also fixes a Dexie anti-pattern in bb/[post_id]: .reverse() before .sortBy() is a
no-op in Dexie; moved array .reverse() to after the await.
Documents the encoding rule and legacy inverted-encoding modules in
GUIDE__SvelteKit2_Svelte5_DexieJS.md and adds mistake #15 to BOOTSTRAP quickstart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds entry #14 to BOOTSTRAP__AI_Agent_Quickstart.md (section 7 "Mistakes
Agents Have Made") and a new "Bootstrap Race" subsection to
GUIDE__SvelteKit2_Svelte5_DexieJS.md ("Common Gotchas"), capturing the
fix from 5fce14980: gate account-scoped liveQuery triggers on
$slct.account_id (non-persisted), not $ae_loc.account_id (persisted,
potentially stale), and treat IDB records from a different non-null
account as a cache miss. Also fixes five pre-existing MD049 emphasis
style warnings (asterisk → underscore) in the Dexie guide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two guards added to the trigger effect:
1. Gate on $slct.account_id being set — prevents the fetch from firing before
the bootstrap Sync Effect has propagated the real account_id. Without this,
get_object's localStorage scavenge read a stale account_id (e.g. 1 from a
prior dev/demo session) and the API returned the wrong account's record.
2. Stale-account detection — if liveQuery returns an IDB row with a non-null
account_id that doesn't match the current account, treat it as a cache miss
and fetch the correct record. Null (global/default) rows are still accepted.
Root cause: ae_loc is a persisted store that hydrates from localStorage before
the bootstrap Sync Effect runs. Old account-specific IDB rows scored highest in
the liveQuery sort, suppressing the trigger and leaving the wrong record visible
until the next full page refresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 'Cvent Splash XLSX (registrant export)' upload mode hitting the new
/event/{id}/badge/import/splash_xlsx endpoint
- Admin controls: begin_at/end_at/return_detail (shared with Zoom mode) +
import_status_filter (splash only, default 'Attending')
- File picker accept attribute switches between .csv and .xlsx per mode
- Set timeout=300000 and retry_count=1 on both server-side upload paths to
prevent false 'no response' failures on slow imports; upsert-by-email on
the backend makes retries safe but a single attempt is sufficient
- Replace misleading 0/0 progress bar with an indeterminate progress bar
during server-side processing; real counter kept for client-side CSV mode
- Show 'Processing on server…' message once upload completes and server work begins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>