$ae_api.api_key does not exist — the field is api_secret_key.
The guard evaluated to true on every render, returning immediately
and never calling search__event_badge. Changed to base_url which
is the standard readiness check per the quickstart doc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
try_cache=false skips db_save_ae_obj_li__ae_obj, so the API fetch
completed but liveQuery never saw the data. Removing the override
lets it default to true so results are persisted to IDB as expected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e.preventDefault() was called for both enable and disable clicks.
On disable, it reverted the DOM back to checked before Svelte could
sync the store update, leaving it visually stuck. Only prevent default
when enabling (to hold the unchecked state during confirmation).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enabling Remote First now shows an inline warning explaining that it
bypasses the local cache and queries the server on every keystroke.
User must confirm before it activates; Cancel leaves the checkbox off.
Disabling still takes effect immediately. Label and checkbox also get
title tooltips. Active state gets a warning tonal highlight.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reports were IDB-read-only — no data appeared until the badge search page
had already populated the cache. Added a background search__event_badge
call (limit 5000, try_cache=false) so navigating directly to /reports
always gets a fresh full dataset; liveQuery updates reactively.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Separators now read "Tuesday · June 9 — EDT" so day-of-week is the
lead identifier, matching how conference staff refer to the schedule.
Added key (sz) to the window-size each block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Date separators now display "Monday, June 9 — EDT" instead of "Jun 9",
using the browser's local timezone abbreviation resolved at page load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Buckets now display newest-first. Naive UTC datetime strings from the
backend are normalized with a Z suffix before parsing so times display
in local browser timezone, matching the badge list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trusted users can already edit title/affiliations in regular mode via the
auth_editable list. Name was locked to trusted+edit_mode only. Staff at the
badge table frequently need to fix a misspelled name mid-queue without
toggling the full edit mode for the whole app.
Added a targeted guard in field_editable(): is_trusted && field === 'name'
passes before the auth_editable list check, so trusted users get the edit
pencil for name in normal mode. Regular authenticated users (attendees)
are unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The default sort (unprinted first, then name) was only wired into the API
order_by_li. Two IDB-local paths were left behind:
1. SCENARIO 2 fallback (no active filters, no text query): used Dexie
.sortBy('given_name') — bypassed entirely on initial page load.
Fixed: fetch .toArray() then JS-sort by print_count ASC → given_name ASC.
2. Fast-path IDB sort default case: also sorted by given_name only, causing
a visible flash of name-ordered results before the API response landed.
Fixed: same print_count ASC → given_name ASC comparator.
All three paths (API, fast-path IDB, fallback IDB) now agree on sort order.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The backend returns naive UTC datetime strings (no Z suffix). dayjs was treating
them as local time, so staff in ET saw UTC times. Added treat_as_utc param to
iso_datetime_formatter (default false, zero breakage to existing call sites) that
appends Z before parsing so dayjs converts to browser-local time on display.
Applied to all print timestamp call sites in badge list (first/last print visible
row + debug row) and to created_on/updated_on in the debug row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the default sort was name-only, mixing printed and unprinted badges
together. Staff at registration need unprinted badges at the top so they can
immediately see who still needs a badge without filtering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The badges page relied on events_slct.event_id being set by ae_acct.slct.event_id
(populated when the user clicks an event in the events list). Direct URL navigation
— bookmark, shared link, browser history — skipped that code path, leaving
events_slct.event_id null. Any call using that value (upload form, create form)
would fire with an empty event_id and get a 404.
Reads page.params.event_id on mount, same pattern used by the launcher and
session pages, so all entry paths are reliable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
runed PersistedState hydrates raw stored JSON without merging defaults, so any
field added after a user's first session is undefined rather than its default value.
Apply a shallow spread (defaults → stored) in the custom serializer for badges,
leads, and pres_mgmt stores so new fields always fall back to defaults for
existing sessions — without disrupting any previously saved preferences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch post-print navigation from window.location.href to goto() for
faster SvelteKit client-side transition back to Badge Search (no full
reload). A nav_to_badges() helper in print controls branches on
badges_loc.print_nav_use_goto (default true).
Badges Config page gains a "Local Device Settings" section with a
checkbox to disable goto() and fall back to hard reload — stored in
localStorage per browser, not synced to the event. Useful as an
on-site escape hatch without a code deploy.
Also fixes the Templates button on the config page: adds the standard
border border-surface-300-700 so it looks like a button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes punch_holes.inset_x_mm in the badge template config UI under
the Punch-Out Hole Markers section. Visible whenever at least one slot
is enabled. Number input (0–8, step 0.5) with a Reset button that
clears back to the 2mm default. Saved to cfg_json on submit; parsed
back on load; omitted from the payload when left at default (null).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Default horizontal inset increased from 1mm to 2mm per side (width shrinks
by 4mm total) so markers stay inside the physical hole slot on all printers
and badge stock with minor registration variance. Vertical stays at 1mm.
The inset is now driven by punch_holes_inset_x (template_cfg.punch_holes
.inset_x_mm, default 2) so it can be tuned per template without a code
change. Added inset_x_mm field to AeBadgeTemplateCfg type with doc comment.
All 9 slot marker divs (6 rainbow + 3 solid) updated via replace_all.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trusted users can now step up to 500 results (300 → 400 → 500) via the
existing ± stepper. Manager tier continues to 2500 as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Report selector now uses a tonal-primary container matching the pres_mgmt
reports pattern, making Long Names / Print Throughput clearly clickable
- All hardcoded gray-* colors replaced with surface-*-* tokens and
dark: variants for proper light/dark mode support
- Control buttons (field selector, threshold, window size) use btn-sm with
visible borders matching the rest of the events module
- Long Names table uses surface tokens on rows, header, borders
- Print Throughput bar rows use surface tokens; expanded badge chips link
to /print instead of /review
- Long Names caps display at 500 rows with a warning note when hit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Changed href from /review to /print, added UserRoundPen icon, and
extended the title to include badge ID and name for hover context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reports link moved out of the enable_mass_print gate into the main
trusted+edit-mode toolbar, to the right of Templates — visible whenever
staff are in edit mode regardless of mass-print config.
Reports page header now shows reprint count in parentheses when > 0,
e.g. "142 badges · 98 printed (7 reprints)".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two IDB-backed reports under /badges/reports:
- Long Names: filter badges by given/family/full name length (threshold
adjustable), colour-coded by severity, links to review page
- Print Throughput: bucket print_last_datetime into 5/15/30/60-min
windows with a horizontal bar chart and expandable badge name list
Also adds a "Badge Reports" nav link on the badges main page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>