Commit Graph

1817 Commits

Author SHA1 Message Date
Scott Idem
f87ab10251 feat(badges): add manual one-off badge create modal
Two-step creation: POST event_person first, then event_badge linked to it.
Badge create route (event_person parent) pending backend fix — frontend is
ready and passing event_person_id + event_badge_template_id in payload.

- ae_events__event_person.ts: new create function (nested under event)
- ae_events_functions.ts: export create_ae_obj__event_person
- ae_comp__badge_create_form.svelte: modal form with live name preview,
  conditional display-name override, template selector (auto-selects when
  only one template), badge_type_code_li derived from selected template's
  badge_type_list JSON, two-step submit status labels
- +page.svelte: load template list via liveQuery, wire Create Badge button
  (edit_mode only), native <dialog> modal with backdrop, remote-first
  refresh on success

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 09:59:46 -04:00
Scott Idem
65e0477761 refactor(build): replace staging/cp env hack with vite --mode per-environment
- Rename .env.staging → .env.dev (and .default template)
- Add .env.test.default for the test tier (test-api.oneskyit.com)
- build:staging → build:dev/test/prod using vite --mode <name>
- deploy:staging → deploy:dev; add deploy:test
- Dockerfile: ARG BUILD_MODE=dev; explicit .env.runtime copy per mode
- .dockerignore: rewritten (deduped); allow .env.dev/.env.test/.env.prod
- .gitignore: track .env.dev.default and .env.test.default
- Remove dead PUBLIC_AE_* imports from ae_stores.ts (ACCOUNT_ID, EVENT_ID,
  NO_ACCOUNT_ID_TOKEN, SPONSORSHIP_CFG_ID); sponsorship_cfg_id defaults to null
- Strip dead vars from .env.prod.default template (AE_CFG_ID, AE_APP_NODE_PORT,
  ACCOUNT_ID, EVENT_ID, SPONSORSHIP_CFG_ID, NO_ACCOUNT_ID_TOKEN)
- GUIDE__Development.md: build:staging → build:dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 16:07:31 -04:00
Scott Idem
99541f0f9d fix(api): add explicit fetch CORS options and response header debug logging
Added mode, credentials, redirect, and cache options to the GET fetchOptions
object. These were previously left to browser defaults, which vary by environment
and can produce opaque CORS failures that are hard to diagnose. Being explicit
avoids environment-dependent surprises.

Also added a try/catch around response.headers logging (log_lvl >= 1) so header
dumps don't throw in environments that restrict header access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 14:05:31 -04:00
Scott Idem
f950c22a59 fix(clip-video): correct false 'Clipped' state on network failure + error UI
get_object() returns false on network failure; the .then() handler was
running with result=false and accessing result.hosted_file_id (evaluates
to undefined, valid JS key, no throw) so all success state was set even
though the request failed.

- Guard result in .then(): if !result.hosted_file_id → set status='error'
- Add 'Failed — Retry?' button state in error branch
- Raise client-side AbortController timeout 300s → 1800s (30 min)
- Add comment explaining root cause (get_object returns false, not throw)

Root cause of the connection drop is proxy_send_timeout or NAT hairpin
timeout (both default 60s) — not a frontend issue; tracked separately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 12:16:07 -04:00
Scott Idem
b63f8eed0c Work on IDAA and Novi auth 2026-03-25 21:13:27 -04:00
Scott Idem
ab294c2a0b Sorry. Quick save to make something live before deadline. 2026-03-25 18:31:39 -04:00
Scott Idem
1de563203d fix(idaa): add reload button to Access Denied screen in iframe mode
WHY: Novi UUID verification is async — on first iframe load the API call
may not complete before the access gate renders, leaving the user stuck on
Access Denied with no way to retry without manually reloading the host page.
The Reload/Retry button calls location.reload() to re-trigger verification.
Only shown in iframe mode where the timing race is the known failure path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 18:04:53 -04:00
Scott Idem
0091fe3ff6 Updates to the documentation about the id_random legacy. 2026-03-25 17:43:15 -04:00
Scott Idem
0ad36a74b2 Fix: system bar hide logic for iframe and menu param overrides (IDAA embed reliability) 2026-03-25 15:49:41 -04:00
Scott Idem
fd244720a7 Update to AE API v3 for the hosted file hash check. 2026-03-25 13:17:25 -04:00
Scott Idem
362136e677 fix(upload): update clip_video endpoint to V3 action path
The legacy /hosted_file/{id}/clip_video route was decommissioned with the
rest of the hosted_file router. Updated to /v3/action/hosted_file/{id}/clip_video.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 12:30:23 -04:00
Scott Idem
a5a806e256 fix(upload): update hosted file upload endpoint to V3 action path
The legacy /hosted_file/upload_files router was decommissioned (commented
out in registry.py). Both upload components now point to the active V3
endpoint at /v3/action/hosted_file/upload. Response shape is identical
so no consumer-side changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 12:11:00 -04:00
Scott Idem
613e43114c fix(idaa): correct reactive loop fix + hide clutter in iframe sys bar
1. Replace incorrect untrack() with idempotent write guard in the
   sys_menu trusted-access effect. untrack() prevents new dep reads but
   ae_loc was already tracked from the outer condition reads, so the write
   still re-notified the effect every run. The guard (only write if value
   != false) breaks the cycle: run 2 finds value already false, skips the
   write, effect stops. Max 2 runs vs the previous infinite loop.

2. Hide auth shield, font-size cycler, and dark/light toggle in the sys
   bar when in iframe mode — host page owns those concerns. Edit mode
   toggle and the main expand button remain visible for staff.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:39:24 -04:00
Scott Idem
1c818e648b fix(idaa): break sys_menu reactive loop + restore menu on iframe=false
Two fixes in the IDAA root layout:

1. Add missing `untrack` import and wrap `$ae_loc.sys_menu.hide = false`
   in `untrack()` inside the trusted-access effect. Without this, reading
   $ae_loc.iframe/$ae_loc.trusted_access and then writing back to $ae_loc
   caused an infinite reactive loop → effect_update_depth_exceeded error.
   Only hit by trusted/admin users in iframe mode (regular Novi members
   at authenticated_access were unaffected).

2. When iframe URL param is explicitly set to 'false', restore
   $ae_loc.sys_menu.hide = false. The root layout sets it to true on
   iframe=true but never resets it, leaving the system bar permanently
   hidden after leaving iframe mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:20:44 -04:00
Scott Idem
5cd1d3b7ad feat(idaa): auto-show sys menu for trusted users in iframe mode
Trusted admins embedded in the Novi iframe can't append show_menu=true
to the src URL, so watch trusted_access reactively and unhide the sys
bar automatically when they authenticate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 22:29:01 -04:00
Scott Idem
66f0efb507 fix(store): guard localStorage calls for Node/SSR builds 2026-03-24 16:46:52 -04:00
Scott Idem
a637343544 chore(ci): add Docker BuildKit examples, .dockerignore, CI cache docs; tune vite config 2026-03-24 16:32:45 -04:00
Scott Idem
a8f3c29b9f Last round of prettier: npx prettier --write src/ 2026-03-24 13:27:40 -04:00
Scott Idem
23d25bf65a Prettier for everything else left 2026-03-24 12:28:28 -04:00
Scott Idem
12a9472064 Prettier for IDAA pages only 2026-03-24 12:28:07 -04:00
Scott Idem
b74c6d0e9c Prettier for Journals 2026-03-24 12:25:22 -04:00
Scott Idem
e1338b1a72 Other areas of the AE SvelteKit primary routes. 2026-03-24 12:18:27 -04:00
Scott Idem
6018a94499 Prettier for Events as a whole. Everything else under that primary directory. 2026-03-24 12:16:44 -04:00
Scott Idem
6e67534454 Prettier for Event ID 2026-03-24 12:16:11 -04:00
Scott Idem
693486bac9 Prettier for Event Pres Mgmt 2026-03-24 12:15:01 -04:00
Scott Idem
6d1d1e2658 Prettier for Event Exhibitor Leads 2026-03-24 12:14:30 -04:00
Scott Idem
7f6e286b73 Prettier for Event Launcher 2026-03-24 12:13:59 -04:00
Scott Idem
a3ed379b17 Prettier for Event Badges 2026-03-24 12:13:37 -04:00
Scott Idem
e9379be5a1 Now even prettier with the new Tailwind CSS plugin. Probably should have done this long ago... 2026-03-24 12:11:25 -04:00
Scott Idem
9a75243d9c Making the code easier to read and more consistent. 2026-03-24 12:05:22 -04:00
Scott Idem
94849137f0 I think pretty much all references to v1 and v2 have been removed. All files have been renamed from _v3 to just the function/var name with out the appended version. Assume no _vX is the current version. 2026-03-24 11:32:06 -04:00
Scott Idem
512e5ef87c Saving more code clean up and removal 2026-03-24 11:15:01 -04:00
Scott Idem
d27ec58fe9 More code clean up 2026-03-24 10:56:31 -04:00
Scott Idem
42358efe7d More code clean up 2026-03-24 10:54:40 -04:00
Scott Idem
8e61bd0ba1 More and more code removal and clean up 2026-03-24 10:42:40 -04:00
Scott Idem
0bc71391fc Cleaning up and removing old legacy code and files 2026-03-24 10:28:54 -04:00
Scott Idem
6e22639e6e fix(api): pass real account_id for lookup requests instead of bypass header
The x-no-account-id bypass was hardcoded to resolve account_id=1 on the
backend, causing account-scoped lookup overrides (e.g. custom country names)
to leak to all callers regardless of their account.

Removing the bypass lets get_object auto-promote the real account_id from
api_cfg, so the backend's existing account filter works correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 20:00:28 -04:00
Scott Idem
a6f8ff709e fix(idaa): fix country/subdivision/timezone dropdowns — switch to in-memory sort
- Country and state/province fields were showing as plain text inputs because
  liveQuery used orderBy() on non-indexed columns, causing silent Dexie errors
  that left the store as undefined indefinitely.
- Fix: replaced orderBy() with toArray() + in-memory sort across all three
  lookup types (country, country_subdivision, time_zone).
- Sort convention matches Aether backend: sort DESC (higher = first, NULL=0
  last), then name ASC — puts priority entries at the top.
- Added db_lookups.ts (IDB schema for lookup tables) and updated core__countries,
  core__country_subdivisions, core__time_zones to IDB-backed SWR pattern.
- Affected: archive edit, archive content edit, recovery meeting edit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 18:44:24 -04:00
Scott Idem
dafe79b3c6 ui(idaa): keep required asterisk inline with label text (embed in inline flex) 2026-03-23 18:23:24 -04:00
Scott Idem
f3ab1c1050 fix(idaa/recovery_meetings): fix weekday chips, recurring fields, and timezone lookup
- Weekday chips: replace bind:checked (unreliable with dynamic bracket notation in
  {#each}) with explicit onchange handlers + class: directives; read weekdays from
  state in submit handler instead of FormData
- Recurring pattern/times: bind select and time inputs to working copy
  so values display and edit correctly
- Times clearing: map empty string to null so times can be cleared once set
- liveQuery guard: skip event_obj sync while edit form is open to prevent
  background refresh from overwriting in-progress user changes
- Timezone lookup: forward order_by_li, limit, offset through the full call chain
  so priority sort and result count params are actually sent to the API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:05:16 -04:00
Scott Idem
5bed167829 Bug fix for saving Zoom info. Removed more old commented out references to on: Svelte 4 code. 2026-03-23 14:27:46 -04:00
Scott Idem
a14320d9ed idaa(recovery_meetings): sanitize Zoom encrypted passcode to avoid saving literal 'null' and normalize related fields 2026-03-23 14:23:29 -04:00
Scott Idem
de8a016bda Minor bug fix to render some icons in HTML text. :-) 2026-03-20 19:25:03 -04:00
Scott Idem
fe23899479 feat: leads QR UX — merged confirm modes, faster scanning, correct capture identity
- Merge Rapid + Qualify scan modes into single Confirm mode with two-button card:
  "Add & Scan Next" (resets) and "Add & View Lead" (navigates to detail). Same
  two-button pattern on the reenable card: "Restore & Scan Next" / "Restore & View Lead".
  Stale 'qualify' localStorage values normalized to 'rapid' via $derived.by().
- QR scanner speed: fps 10→25, qrbox 82%→88%, useBarCodeDetectorIfSupported (native
  BarcodeDetector API on Chrome/Edge — significantly faster than ZXing JS fallback)
- Fix capture identity stored in external_person_id / group:
  licensed exhibit user → their email; shared passcode → 'shared_passcode' label
  (not the raw passcode); Aether user bypassing exhibit sign-in → access_type string
  ('trusted', 'manager', 'super', etc.). Consistent across all three lead capture
  components (single scanner, multi scanner, manual search).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 19:15:35 -04:00
Scott Idem
6662e82f40 feat: leads re-enable flow — detect removed leads on scan + Remove/Restore buttons
- QR scanner (single + multi): detect previously-removed leads via IDB enable flag;
  route to 'reenable' state instead of duplicate error; offer Re-activate button
- API fallback: if create fails and no IDB record, search API for disabled tracking
  record by event_exhibit_id + event_badge_id (adds qry_badge_id param to
  search__exhibit_tracking)
- Lead detail page: Replace raw enable checkbox with Remove Lead (two-click confirm,
  navigates back after) and Restore Lead card (shown when enable is falsy)
- Fix flash of disabled records in leads list: filter !enable in both filtered_lead_li
  derived and local IDB fast-path in handle_search_refresh
- eslint.config.js: disable svelte/no-navigation-without-resolve (no base path configured)
- Also includes _random field annotation cleanup (db_events, ae_types), iframe layout
  fixes, badge view tweaks, test updates, and doc updates from prior session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 18:18:10 -04:00
Scott Idem
4586e809d7 fix: leads scanner — handle falsy API result to prevent frozen 'adding' state
When create_ae_obj__exhibit_tracking returns false/null (API down, network
error, auth failure), the scanner was left frozen at 'adding' indefinitely.
Added else branch to surface an error state in both single and multi scanners.

Also fixes multi scanner which wasn't capturing the API return value at all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 16:36:59 -04:00
Scott Idem
334c3a21bc feat: leads QR scanner — Auto/Multi modes, 4-mode fancy selector, UX polish
Scanner modes (now 4, persisted per exhibit):
- Rapid:   confirm tap → auto-reset (existing, fixed)
- Qualify: confirm tap → navigate to lead detail (existing, fixed)
- Auto:    badge found → auto-add immediately, no confirmation tap needed
- Multi:   BarcodeDetector batch scan → responsive grid of confirm cards

Multi scanner (new ae_comp__lead_qr_scanner_multi.svelte):
- Native BarcodeDetector API (Chrome/Edge/Safari 17+); Firefox fallback message
- 16:9 viewfinder with corner guides + "Align up to 4 badges flat" overlay
- Capture Batch tap → up to 8 QR codes detected in one frame
- Per-card states: loading skeleton, ready (Add/Skip), blocked (opt-out),
  already-captured (View/OK), adding spinner, success (auto-fade), error
- Add All (N) bulk action; cards fade+scale out smoothly on dismiss

Mode selector (ae_tab__add.svelte):
- Replaces Rapid/Qualify toggle with collapsible 4-mode fancy select
- Trigger shows active mode icon (color-coded) + name + description
- 2×2 options grid expands on tap, closes on selection

QR scanner element (element_qr_scanner_v3.svelte):
- object-fit: cover eliminates 4:3 camera letterbox dead zone
- 7-second start timeout with actionable error message
- Starting/error overlays with high-contrast styling
- Try Again button with RefreshCw icon

Style guide updated: icon+text button rule (§8), btn/preset-filled workaround (§12)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 16:30:38 -04:00
Scott Idem
14c2635df4 docs + fix: Leads module doc, badges onsite doc, license access fix, QR log cleanup
- Add MODULE__AE_Events_Exhibitor_Leads.md — full module reference (auth model, tabs,
  data model, routes, offline/PWA notes, OSIT admin notes)
- Add MODULE__AE_Events_Badges_Onsite.md — onsite printing guide (browser settings,
  CUPS/Linux setup, Zebra ZC10L section with test results, Epson stub, troubleshooting)
- Archive PROJECT__AE_Events_Exhibitor_Leads_v3*.md + Zebra test day doc → history/
- Fix leads license management access: ae_tab__manage.svelte license section now visible
  to administrator_access OR shared-passcode sign-in (type === 'shared'); matches spec
- Add type field to LeadsLocState.auth_exhibit_kv interface (was set at runtime but missing from type)
- Silence QR code console noise: entry log gated at log_lvl >= 2, data URL log removed
- vite.config.ts: exclude documentation/ and tests/ from Vite HMR file watcher

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:58:57 -04:00
Scott Idem
9673cbefe3 fix: PWA install prompt — capture beforeinstallprompt at module load time
The browser fires beforeinstallprompt very early (~1s after page load),
before Svelte's $effects run. Moving the event listener registration to
module level ensures we never miss the event regardless of when init()
is called from the root layout.

init() now only handles dismiss state (localStorage) and standalone
detection (DOM) — both safe to defer until after component mount.

Platforms:
- Chrome / Chromium / Android: native install button via captured prompt
- iOS Safari: manual Share → Add to Home Screen instructions (unchanged)
- Firefox desktop: no beforeinstallprompt support (browser-level limitation);
  Firefox shows its own install button in the address bar automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:14:17 -04:00
Scott Idem
9a43879535 feat: leads — add show/hide hidden records toggle + update stale README gaps
- Add Eye/EyeOff toggle to exhibit tracking search bar; wired to
  existing $events_loc.leads.show_hidden store field
- Guard + init the show_hidden field in +page.svelte
- Add show_hidden to search_params so toggling triggers a refresh
- Apply hide filter in local IDB search path (skip hidden unless toggled)
- Pass hidden: 'not_hidden' | 'all' to API search__exhibit_tracking call
- Apply hide filter in filtered_lead_li for the broad liveQuery fallback path
- README: remove stale allow_tracking/PWA/export gaps; add Implemented section;
  fix ae_events_functions import path (moved to ae_events/ subdir)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:09:16 -04:00