Replace stale deploy:staging/deploy:prod references with current
build:docker:*, deploy:remote:*, and .env.dev/test/prod file names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- deploy:dev/test/prod → build:docker:dev/test/prod to distinguish
local Docker builds from remote server deploys
- Add deploy:remote:test and deploy:remote:prod — SSH to linode.oneskyit.com
and run deploy.sh on the server
- Trailing whitespace cleanup in .env.*.default files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The staging default template had real OSIT account_id and event_id values
in inline comments. These are not secrets but shouldn't be in a committed
template — they'd be misleading on any non-OSIT deployment.
Replaced with plain XXXX placeholders.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added section 7 covering /v3/action/user/ endpoints: authenticate, verify_password,
change_password, new_auth_key, email_auth_key_url — including the legacy→V3
migration table and auth key one-time-use behavior.
Also clarified the response shape note to explicitly list all response types
(GET single, GET list, POST create, PATCH, search) that use the V3 envelope.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
tests/README.md — new "IDAA Auth Tests" section with three lessons:
1. ae_idaa_loc seed must include full bb/archives structure or
verify_novi_uuid() throws silently and resets novi_uuid to null
2. StorageEvent pattern for testing reactive persisted-store updates
without pre-seeding Dexie or navigating twice
3. getByText { exact: false } for UUID in multi-field spans
GUIDE__SvelteKit2_Svelte5_DexieJS.md — new "untrack() reactive tracking
trap" section: reading a store value inside untrack() makes it a one-shot
dependency; fix is to hoist the read outside untrack() and add a guard
to avoid redundant work on unrelated store updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers 5 scenarios with extensive inline comments explaining business
context and the 2026-03-25 stale-cache root-cause fix:
1. Auth gate (Sev-1 regression guard) — no UUID → Access Denied
2. Happy path — valid UUID + fresh cfg → access granted
3. Invalid UUID — Novi 404 → Access Denied
4. Stale cache — StorageEvent delivers fresh site_cfg_json →
Effect 2 retries verification without reload (tests the reactive
tracking fix in (idaa)/+layout.svelte)
5. iframe mode — Reload/Retry button visible on Access Denied
Key lesson found while writing: ae_idaa_loc seed must include the full
bb object or verify_novi_uuid() throws on bb.qry__hidden assignment,
caught silently, resetting novi_uuid to null even after a successful
Novi API call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
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>
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>
- 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>
Fulfills Phase 4 open item from PROJECT__AE_Object_Field_Editor_V3_upgrade.md.
Covers import, basic text usage, all field types, select with nullable FK,
key props table, and behavior notes (optimistic display, edit_mode visibility).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PROJECT__AE_Object_Field_Editor_V3_upgrade.md: mark datetime support complete (was already implemented, just not checked off)
- AE__Components.md: replace stale ae_crud entry with element_ae_obj_field_editor_v3 description; correct TipTap "marked for removal" note (TipTap is actively used)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
element_ae_crud.svelte and element_ae_crud_v2.svelte had zero active
importers; only a commented-out reference remained. Moved both to trash
and removed the dead comment from ae_comp__event_presentation_obj_li.svelte.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relocates the functions file from lib root into its module directory,
matching the pattern used by all other modules (ae_journals, ae_archives, etc.).
Updated all 85 import paths from \$lib/ae_events_functions → \$lib/ae_events/ae_events_functions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add justify-center + h-6 to the debug info row above the badge so it stays centered
and doesn't cause vertical shift when conditional elements show/hide on edit mode toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add field_shown() and field_editable() functions driven by event_badge_template.other_json:
controls_cfg: { shown?: string[], auth_editable?: string[] }
Access rules:
- No authenticated_access → display-only, no edit buttons shown
- authenticated only → can edit fields in auth_editable (default: title/affiliations/location/allow_tracking/pronouns)
- trusted + edit_mode → always sees and edits all fields, ignores config
Each attendee field card (name, title, affiliations, location, allow_tracking, pronouns)
is now wrapped in {#if field_shown()} and its edit button/accordion gated by field_editable().
No backend changes needed — other_json is an existing longtext JSON column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add name_two_lines toggle (default: true) — uses CSS horizontal padding scaled by
character count to coax short names (e.g. "Scott Idem") into a natural two-line wrap
without a hard <br>; three tiers: ≤12 chars (18%), ≤20 (8%), ≤28 (2%), >28 no pad
- Inner <div> (block element) used inside Element_fit_text for class: directives —
Svelte scoped CSS requires static class names in the template; dynamic strings and
class: on component elements both fail to match scoped CSS rules
- Add leading-none to all four Element_fit_text fields (name, title, affiliations,
location) — line-height must be set at the wrapper div level where fit_text measures
scrollHeight, otherwise the binary-search scaler returns inflated sizes
- name_two_lines state persisted to localStorage (ae_badge_print_tweaks key) alongside
existing print_offset, hide_chrome, and banner_full_width tweaks
- Rewrite badge_header_calibration.svg as a precise SVG ruler with labeled tick marks
(major at 1in intervals, minor at 0.25in) for accurate physical print calibration
- Gate debug outline CSS on html.debug_outlines class (set by controls panel) so
outlines never appear in normal print mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 4 badge test files had identical ~35-line beforeEach blocks (pageerror listener,
inline V3 route mocks, addInitScript localStorage seed). Replaced with two helpers
in minimal_v3_mocks.ts:
seed_trusted_session(page, event_id, account_id?)
— seeds ae_loc localStorage with trusted/manager auth via addInitScript;
account_id defaults to testing_account_id
setup_badge_test_page(page, event_id)
— one-call beforeEach: pageerror listener + attach_minimal_v3_routes +
seed_trusted_session
Each test file's beforeEach is now 1-3 lines. All 12 tests still pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MODULE__AE_Events_Badge_Templates.md: mark style_href, duplex, and
badge_back suppression as done; correct v1→v2 component references
- PROJECT__AE_Events_Badges_Review_Print.md: update project status
- TODO__Agents.md: close debug-outlines, style_href, duplex TODOs;
mark window.print() wiring as done
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Debug outlines were applying to all print jobs. Now scoped to
html.debug_outlines so they only appear when the "Show debug outlines"
checkbox is active in the controls panel (trusted users only).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause fix: tests/_helpers/ae_defaults.ts was missing __version: 1, causing
store_versions.ts to wipe ae_loc from localStorage on every test page load. This
made trusted_access fall back to false, hiding the print button (can_print guard)
and failing all attendee workflow tests.
Changes:
- ae_defaults.ts: add __version: 1 with comment explaining the store_versions guard
- idb_helpers.ts: extract inject_badge_and_template() from print layout test into
shared helper; now used by all three print/render test files
- event_badge_render.test.ts: new — 4 tests covering full_name_override priority,
full_name fallback, duplex=0 hides badge back, duplex=1 shows badge back
- event_badge_attendee_workflow.test.ts: cleaned up (diagnostic code removed);
all 3 tests now pass
- event_badge_print_layout.test.ts: renamed from badge_print_layout.test.ts;
inline inject_idb() replaced with shared idb_helpers import
- event_badge_smoke.test.ts: renamed from event_badge.test.ts
- playwright.config.ts: use system /usr/bin/chromium on Arch Linux (avoids
Playwright's bundled Chromium which requires Ubuntu libs not present on Arch)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iframe=true now hides the sys bar for all users (previously trusted_access
users still saw it). Admins can pass show_menu=true to re-enable it while
testing an embedded page like video_conferences.
hide_menu=true remains for non-iframe hide use cases (kiosk, etc).
Updated URL builder: hide_menu checkbox → show_menu checkbox.
Updated GUIDE__Development.md URL params table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ae_ prefix belongs on Svelte component/variable names, not URL params.
Updated both the consumer (+layout.svelte) and the builder (jitsi_url_builder).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The URL builder generates ae_hide_menu=true but nothing consumed it.
Now layout.svelte reads the param on mount and sets $ae_loc.sys_menu.hide,
which flows through bind:hide into E_app_sys_bar's class:hidden.
Applies even for trusted_access users who bypass the iframe guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ae_hide_menu=true query param option to suppress the AE navigation
chrome when embedding the Jitsi video conference page in Novi or other
host pages that provide their own navigation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New component ae_idaa_comp__jitsi_url_builder.svelte builds and previews
Jitsi iframe URLs for testing and Novi page configuration. Features:
- Environment selector (prod / dev / local / custom)
- Room name, Novi UUID, site key inputs
- Moderator toggle (explains JWT + logging implication)
- Advanced: domain, start muted/hidden, all 5 sound settings
- Output in URL or iframe HTML snippet mode with copy button
- "Open in new tab" for quick testing
Embedded on jitsi_reports page as a collapsible panel, gated to
trusted_access users only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jitsi_reports was previously at src/routes/idaa/jitsi_reports/ and
was not protected by the (idaa) layout auth gate. Moved to
src/routes/idaa/(idaa)/jitsi_reports/ — same URL, now requires
trusted_access or Novi-verified authenticated access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a passcode matched, entered_passcode was cleared and the trigger
was set, but show_passcode_input was never set to false. This left the
input visible so users could keep typing after access was granted.
Set show_passcode_input = false immediately after clearing entered_passcode
on a successful match, consistent with the intent described in the
handle_clear_access() function which resets it to true on clear.
Export button now only renders when event_exhibit.leads_api_access === true,
preventing a 403 that would always fire otherwise. Endpoint confirmed live on
backend. TODO updated to reflect export + allow_tracking gate both resolved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated download_export__event_exhibit_tracking to call
/v3/action/event_exhibit/{exhibit_id}/tracking_export instead of the
legacy /event/exhibit/{exhibit_id}/tracking/export (V1 path).
Added doc comment listing the expected export columns so the backend
agent has the full field spec when implementing the endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- QR scanner: after badge loads, blocks add with 'Tracking Opt-Out' warning
card if allow_tracking !== true; replaced deprecated CheckCircle → CircleCheck
- Manual search: shows ShieldOff 'Opt-Out' label per row for blocked badges;
add_as_lead() also guards against programmatic bypass
- Fix: ae_comp__exhibit_tracking_obj_li — Loader2 from wrong package
@lucide/svelte → LoaderCircle from lucide-svelte
- ae_types.ts: added allow_tracking and agree_to_tc to ae_EventBadge interface
- README.md (leads): full rewrite reflecting actual current state and known gaps
- TODO__Agents.md: updated Leads entry from stale 'NEXT MAJOR FEATURE' to
accurate in-progress status with remaining checklist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ae_stores.ts
- Extract auth/identity section (~80 lines) into ae_stores__auth_loc_defaults.ts
- Spread auth_loc_defaults into ae_app_local_data_defaults (zero consumer changes)
ae_events_stores.ts (both loc and sess structs)
- badges → ae_events_stores__badges_defaults.ts
- launcher → ae_events_stores__launcher_defaults.ts
- leads → ae_events_stores__leads_defaults.ts
- pres_mgmt → ae_events_stores__pres_mgmt_defaults.ts
Each new file exports *_loc_defaults and *_sess_defaults. The store files
now reference these by name instead of embedding inline objects. All
$ae_loc.* and $events_loc.* consumer paths are unchanged.
svelte-check: 0 errors, 0 warnings
- Remove unused `import { offset } from '@floating-ui/dom'`
- Remove ver_idb constant and field (same as ae_stores / ae_events_stores)
- Remove commented-out personal Novi UUIDs (security hygiene — these belong
in site_cfg_json on the server, not in source; idaa layout already reads
them from $ae_loc.site_cfg_json and writes to idaa_loc at mount)
- Add comment explaining novi_admin/trusted/jitsi_mod_li are server-driven
- Remove dead writable/persisted alternatives and console.log lines
- Remove stale 'Updated 20xx-xx-xx' date comments
- Condense redundant tracking comments to single-line form
Verified all FA spans and variant-* patterns in launcher files are inside
HTML comment blocks — no live FA or legacy variant classes remain.
The freeze was precautionary; the actual migration was already done.
Phase 3 for Launcher is now limited to UX/card styling polish only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Marked all non-frozen, non-IDAA style migration work as done:
- FA→Lucide across events, pres_mgmt, core, badges, leads, hosted_files
- variant-* → preset-* across all modules
- badge code_to_icon refactor, FA CDN scoped to IDAA layout
- global svg.lucide inline fix in app.css
Phase 3 deferred: Launcher (post-April 2026) and IDAA (last priority).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lucide renders <svg> elements which default to inline-block in browsers,
causing icons to break onto their own line when mixed with text — unlike
FA spans which were display:inline.
Added svg.lucide { display: inline; vertical-align: middle; } to app.css
so all Lucide icons flow inline with adjacent text globally, matching the
FA icon behavior without needing class="inline" on every icon instance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the FA 5.15.4 CDN <link> from app.html (global) into
src/routes/idaa/+layout.svelte <svelte:head> so it only loads
on /idaa/* routes. All other modules now use Lucide exclusively.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert 6 template-level FA spans to Lucide components (Star, Biohazard,
Asterisk, Wifi). The code_to_html JS string dict (dietary symbols used
with {@html}) retains FA spans since they are raw HTML strings, not
Svelte template markup — FontAwesome CSS (app.html CDN) renders them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ae_idaa_comp__event_obj_id_edit.svelte: the component <style> block used
@reference + @apply for ~10 local classes (.section-card, .field-label,
.toggle-chip, .day-chip, etc.). svelte-check's CSS language service does
not understand Tailwind v4 @reference/@apply directives and emitted 23
'Unknown at rule' warnings.
Fix: all local class usages inlined as Tailwind utility strings directly
on each element (~80 template sites). The <style> block is removed.
Conditional classes on toggle-chip/day-chip converted to ternary expressions.
svelte-check now reports 0 errors and 0 warnings across all files.
Svelte 5 does not support <style> or conditional {#if} blocks wrapping
<style> tags inside <svelte:head>. The parser treats them as raw-text
elements and reports '<script> was left open' at EOF.
Fix:
- Print media CSS moved to static/ae-print-badge.css (plain static file,
no framework magic needed — all selectors target global elements).
- svelte:head now uses a simple <link> to that file.
- $effect injects the @page size dynamically per template layout field,
avoiding the Svelte 5 parser limitation for conditional style injection.
- Badge_template interface in db_events.ts: added cfg_json / data_json
(standard Aether object fields that were missing from the type).
Replaces all FontAwesome <span class="fas/fab fa-*"> with Lucide Svelte
components across 20 launcher files. launcher_cfg_section.svelte icon prop
changed from FA string to AnyComponent (svelte:component for dynamic render).
Dynamic file-extension icon now uses ae_util.file_extension_icon_lucide().
Fixes class: directives on components (invalid in Svelte 5) → ternary class.
Removes title prop from Lucide components → wrapping <span title="...">.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Defaults to true (authenticated_access required) — no change to existing consumers
- Launcher file buttons set require_auth=false to allow unauthenticated downloads
Replaces all alert() calls in the user/pass auth flow with reactive state.
Button shows: Verifying… (disabled) → Failed — retry? (red) →
Enter credentials first (amber) → Username/User ID Sign In (default).
Error messages (wrong password, no person record, no server response)
appear as small text below the button on failure.
Clicking the button resets to default so retry is clean.
Also removes dead commented-out alert and cleans up the promise chains.
No type="button" issues found — all non-submit buttons were already typed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the debug alert() calls from the email magic-link flow.
Button now shows live feedback inline:
- 'Sending…' while the lookup is in flight (disabled + cursor-wait)
- 'Email sent ✓' on success (green fill)
- 'No account found' if no user matches the email
- 'Error — retry?' on network/API failure
- 'Enter an email first' if submitted empty
Clicking the button while showing a result resets it to the default label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign In/Out (e_app_sign_in_out.svelte):
- Remove redundant internal header (sr-only was broken by :global CSS override)
- Full-width form inputs and buttons with 'or' divider between the two methods
- Signed-in state shows centered username and full-width action buttons
Access/Passcode (e_app_access_type.svelte):
- Fix 'Locked' button: was running trigger=true (no-op permission reprocess);
now correctly toggles show_passcode_input so the input shows/hides on click
System bar (e_app_sys_bar.svelte):
- Dynamic section headers: Sign In/Out shows username when signed in;
Access/Passcode shows ShieldEllipsis/ShieldMinus/ShieldUser based on state
- Fix passcode input not showing on re-open via menu button:
onDestroy resets show_element__passcode_input=false; toggle_expand now
restores it to true for anonymous/no-access state (matches handle_shield_click)
- Broaden anonymous check from === 'anonymous' to !access_type || === 'anonymous'
- Remove dead getElementById focus call (DOM not ready at that point;
focus_input binding in Element_access_type handles it correctly)
- Appearance section: mode/font buttons at top, dark mode gets amber tint
in light mode for visual context, theme select uses text-sm
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- [journal_id]/+page.svelte was rendering a second Journal_obj_id_edit
alongside the one already in ae_comp__journal_obj_id_view.svelte,
causing the modal to open twice simultaneously.
- ae_comp__journal_obj_id_view: changed show={} to bind:show={} so that
closing the modal properly writes back to journals_sess, preventing the
store from fighting the close and re-opening it.
Flowbite's built-in outsideclose was unreliable for this side-drawer setup.
Root cause: Flowbite's _onclick handler uses dlg.getBoundingClientRect() to
detect backdrop clicks. This works in theory for showModal() dialogs, but
something in the Svelte transition / stacking context prevented it firing.
Fix: bypass Flowbite's detection entirely.
- onclick prop on <Drawer> is spread through Flowbite's restProps chain
to the native <dialog> element, overriding the broken _onclick handler.
Handler: just closes the drawer unconditionally.
- A stopPropagation wrapper div around all visual panel content ensures
that clicks INSIDE the panel never bubble up to the dialog element.
Only genuine backdrop clicks (outside the visual panel area) reach
the dialog and trigger the close handler.
ESC key is unaffected — it fires oncancel not onclick.
- Hover info strip is always in DOM (opacity-only toggle, no {#if} mount)
so first hover no longer triggers a layout recalc/flash
- Bar strip gets fixed h-9 height so inline label text appearing on
group-hover can never shift the bar vertically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hover info strip is now absolute-positioned above the bar (opacity fade
with delay-500) so it never shifts the bar layout — fixes the bounce
- Panel widened to w-80 with overflow-x-hidden — fixes horizontal scroll
caused by sub-components hardcoding w-72 inside the padded panel
- All panel sections are now collapsible (Access open by default, others
closed) — reduces vertical crowding; matches launcher_cfg pattern
- Section headers show current state inline (access level, theme name/mode)
- Admin section groups cfg + debug trigger together cleanly
- Bar transitions use duration-200 for snappier feel without bounce
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New compact bar + expandable panel design:
- Compact strip (bottom-right): auth shield, font cycler, dark/light toggle,
edit mode toggle (authenticated+), menu expand — icon-only by default,
labels reveal on hover
- Hover info strip shows person name + access level when logged in
- Expanded panel: sign in/out, access/passcode, appearance (theme), admin
(config + URL builder + debug trigger) — all gated same as before
- Debug overlay trigger moved into admin section (edit mode only)
- All business logic preserved via existing sub-components (unchanged)
- e_app_sys_menu.svelte retained but no longer mounted (import commented out)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New e_app_url_builder.svelte component lets admins construct and copy
shareable URLs with any combination of core global params (iframe, theme,
theme_mode, key). Outputs full URL by default; toggleable to params-only
string for pasting onto existing links. Integrated into e_app_cfg Utilities
section (visible in edit mode).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Flowbite Dialog component (which Drawer wraps internally) renders a
<CloseButton type='submit'> by default when dismissable=true. Since the
Drawer does not use the form/dialog mechanism, that button appeared at the
bottom of the drawer but did nothing. Fix: dismissable={false} on the cfg
Drawer suppresses it.
launcher_cfg.svelte footer redesigned:
- Lower-left: Close button (always visible) — mirrors top-right X, useful
when the user has scrolled down through long config sections
- Lower-right: Reload (always visible, shorter label)
- Full-width Debug Panel button (edit_mode only, below the row)
Click-outside behavior unchanged — Flowbite Dialog outsideclose defaults
to true, so tapping outside the drawer still closes it.
- launcher_cfg_section.svelte: Remove md:grid-cols-2 from content wrapper.
Root cause of the middle-width layout issue: at md breakpoint the drawer
is only 384px wide but the section body switched to 2-column, cramming
full-width content blocks into ~170px each. Always grid-cols-1 now.
- launcher_cfg.svelte: Rename Hardware tab to Device (neutral — applies
even in browser). Reorder Device tab content: Sync Timers first (relevant
to all devices), then native sections behind $ae_loc.is_native || edit_mode.
Updates still hidden behind is_native only (no useful preview).
- launcher_cfg_native_os.svelte: Add dev-preview banner when edit_mode is
on but not running in Electron. electron_relay.ts guards all calls with
'if (!native) return null' so there are no import errors or crashes —
controls simply show with a warning indicator. Removes stale placeholder
comment left by a previous agent.
- Setup (default): Oral/Poster preset, WS Controller, Screen Saver
- Hardware: Electron health/OS/updates (native only) + Sync control
- Dev: Local reset/debug tools — tab hidden until Edit Mode is on
Edit Mode toggle added as subtle pencil icon in the cfg drawer header
(low opacity when off, primary-colored when active). This is intentional
— onsite kiosk operators should not stumble on it, but admins doing
setup can find and toggle it without leaving the drawer.
Dev tab visibility and the Debug Panel button both gate on edit_mode,
keeping the default view clean for non-technical operators.
Adds a two-button Session Mode Preset toggle in Display & App Modes cfg:
- 'Oral / Default' restores all menus/headers/iframe off
- 'Poster Kiosk' sets iframe=true + hides menu, header, footer
When WS is connected (local_push or remote controller), tapping a preset
sends ae_mode:poster / ae_mode:oral to all connected devices so an operator
can reconfigure the whole room from one device.
ae_mode:{poster|oral} command handler added to handle_ws_recv() in
+layout.svelte — receives and applies the same preset on remote devices.
When on a Poster Session in Pres Mgmt, the Launcher nav link now appends
?iframe=true&launcher_menu=hide so the recipient opens a clean kiosk view
without the site header or left session panel.
- ae_comp__events_menu_nav: add events__launcher_extra_params prop; update
launcher_sess_qry to merge extra params after session_id
- session_page_menu: derive is_poster from type_code and pass the kiosk
params into the Launcher link
events/+layout.svelte:
the events nav is actually rendered. Adds pt-0 for the launcher case
(nav never rendered there; launcher manages its own header offset).
- pb-48 (fixed 12rem) → pb-[25vh] — scrolls ~25% of viewport below
last card on any screen size, scales properly on phone/tablet/desktop.
launcher/+layout.svelte outer wrapper:
- Static mt-4 replaced with conditional:
mt-12 when launcher header is visible (matches h-12 header height,
keeps content clear of the absolute overlay)
mt-2 when launcher header is hidden (minimal breathing room only)
- Result: iframe mode + launcher_header=hide → near-zero top dead space
Allows sharing a clean link that auto-configures display state on any
device — no manual setup required, ideal for tablet PWA kiosk deployment.
All four params are optional and independent. Values persist in
localStorage so they survive reloads; the URL param always wins when
present. Read inside the existing reactive URL-sync $effect, which
fires on every SPA navigation (unlike the root onMount which only
fires once on initial load).
Supported params:
?iframe=true/false — hide/show global sys & debug menus
?launcher_menu=hide/show — hide/show left session/location panel
?launcher_header=hide/show — hide/show 'Æ Launcher v3' header bar
?launcher_footer=hide/show — hide/show the status footer
Example clean poster-kiosk link:
/events/{id}/launcher/{loc_id}?session_id={sess_id}
&iframe=true&launcher_menu=hide&launcher_header=hide
- Add (pres_mgmt)/+layout.svelte with shared section wrapper (max-w-7xl mx-auto)
so all child pages center correctly on wide viewports
- Strip per-page outer <section> tags from session, presenter, location pages;
replace with inner <div max-w-7xl mx-auto> for detail constraint
- Restructure all page menus from flex-row to flex-col so nav bar occupies its
own row and options/actions sit in a separate justified row below — prevents
unwanted wrapping when nav is w-full
- Standardize Æ Core button visibility to edit_mode && manager_access across all
menus; update button style to ae_btn_warning for visual distinction
- Add w-full + justify-between to ae_comp__events_menu_nav outer div
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create launcher_session_view_posters.svelte — a touch-first card-grid
layout for Digital Poster sessions, designed for tablet/phone PWA use.
Layout:
- 1 column on mobile, 2 on sm, 3 on xl
- Each poster card: title (line-clamp-3) + presenter name/affiliation +
'Open Poster' action button at card bottom
- Poster code (or 1-based index fallback) badge in top-right corner
- Card active:scale-[0.98] for tactile touch press feedback
- Sticky compact session header strip with name, code, and poster count
- Optional 'Session Resources' strip for rare session-level files
- overflow-y-auto + grow so the grid scrolls; header strip stays fixed
Integration:
- launcher_session_view.svelte: import + delegate when type_code==='poster'
- launcher_file_cont.svelte: min-w-96 → w-full on poster button so it
fills its container (card or list row) without overflow on small screens
- WS open/close/zoom command pipeline unchanged (all in launcher_file_cont
and +layout.svelte which were not modified for the WS paths)
The event_session_type_code field on file objects is often null, causing
poster-mode files to fall back to 'oral' and render as download buttons
instead of modal openers.
- launcher_session_view: session-level files now use derived type_code
- launcher_presenter_view: add session_type prop (default 'oral'); parent passes type_code
- launcher_presenter_view_posters: hardcode poster/modal (only ever rendered in poster context)
- launcher_menu event/location files unchanged: no session context, field is the only signal
Session ID page: replace `container mx-auto` with `w-full` — removes the
Tailwind breakpoint max-width cap that was narrower than the outer layout's
max-w-7xl on smaller desktop viewports, and eliminates the double-padding
from nested container classes.
Presenter ID page: replace the conflicting `md:container items-center mx-auto
h-full min-w-full max-w-max` with a clean `w-full`. The `items-center` was
the main bug — in flex-col context it horizontally centered children (presenter
details, file upload form, file list) at their content width instead of
stretching them to fill the available space. Removes all of the conflicting
min/max width overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add modal_zoom_fit state (default: fit); resets on every new poster open
- Zoom/Fit toggle button + image double-tap on controller tablet
- Both zoom triggers send ae_zoom:fit/zoom over WS to remote display (local_push)
- ae_zoom: handler added to handle_ws_recv() for remote device
- Replace 3 scattered close buttons with single bottom control bar:
- [Zoom/Fit] always visible on controller
- [Close Both] sends ae_close WS + clears local modal (local_push only)
- [Back to List] clears local modal only; remote keeps showing current poster
- Bottom bar hidden on controller=remote (kiosk display-screen mode)
- native pinch-to-zoom via touch-action: pinch-zoom on img (no JS library needed)
- pb-14 on modal bodyClass prevents buttons from overlapping poster content
Adds a 🎨 button to the badge debug bar (edit-mode only) that cycles
through 5 SVG background patterns keyed by badge type color palette:
stripes, dots, diamonds, grid, swirls.
Swirl tile is 64×64 with corner-to-corner cubic bezier S-curves so
edges align seamlessly when tiled. Off by default; never prints.
- ae_comp__badge_obj_view_v2.svelte: removed all inline edit-mode logic
(floating Edit/Save/Cancel panel, placeholder list, input fields, save/cancel
functions). V2 is now purely a display component — editing happens in the right
panel (ae_comp__badge_print_controls.svelte) via liveQuery reactivity.
display_* values are now $derived directly from lq__event_badge_obj.
Fixes effective_badge_type_code CSS class (was always empty in previous v2).
- ae_comp__badge_print_controls.svelte: added "Print Badge" button at the top of
the panel. Increments print_count, fires window.print(), then navigates to badge
search. This is now the canonical print action for v2; the header "Print Now"
button is a shortcut that calls window.print() only (no count tracking).
Clarified $ae_loc.edit_mode comment — global AE Edit Mode is not a badge-specific
edit toggle; used here only to gate reprints.
- print/+page.svelte: added clarifying comments on is_edit_mode (global vs local)
and the header "Print Now" button (shortcut only, no count tracking).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds binary-search font auto-scaling for badge text fields, replacing
the character-count heuristic in v1. New files:
- action_fit_text.ts: Svelte action using binary search + MutationObserver
+ ResizeObserver. Pass null to disable (manual override mode).
- element_fit_text.svelte: Component wrapper with min/max/manual_size/
height/width props. height prop required for overflow detection to work.
- ae_comp__badge_obj_view_v2.svelte: Badge render using Element_fit_text
for name/title/affiliations/location in display mode. font_size_* props
default to undefined (auto-scale) instead of numeric defaults.
fit_heights derived object provides layout-aware section heights for
badge_3.5x5.5_pvc, badge_4x5_fanfold, and badge_4x6_fanfold layouts.
flex_justify() maps shorthand ('around','between','even') to CSS values.
Edit mode uses plain divs — inputs are never auto-scaled.
print/+page.svelte: Added v1/v2 toggle button in header. V1 preserved
as fallback. font_size_* passed as null (not ?? undefined) to v2 so
auto-scaling is active by default; manual override from print controls
still disables it per-field.
Docs: PROJECT__AE_Events_Badges_Review_Print.md updated with kiosk
workflow design intent, email address rule (always event_badge.email),
permission model alignment gap (TASK 4.0), and v2 implementation status.
TODO__Agents.md: completed items removed, badge polish tasks updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Generate and persist crypto.randomUUID() as controller_client_id on first
launcher load (events_loc is persisted so it survives page reloads).
Previously fell back to Date.now() on every reload.
- ae_open: handler now resolves presentation name via Dexie lookup chain:
file.for_id -> presentation.name, falling back to file.filename.
Remote modal now shows the presentation title instead of raw filename.
- launcher_file_cont: poster 'Open Poster' click now sends ae_open:event_file
command to remote display when controller=local_push and WS is connected
- +layout.svelte handle_ws_recv: ae_open: handler now looks up the file obj
from Dexie after setting modal__open_event_file_id, so the remote modal
has the hosted_file_id needed to render the poster image (was showing
'No image selected' on remote device)
- Add ae_auth_error writable store to ae_stores.ts
- Wire api_get_object, api_post_object, api_patch_object to set
ae_auth_error on 401/403 (browser-only guard, never fires SSR)
- Root layout watches ae_auth_error; only raises flag_expired when
a JWT is present (prevents false trigger on unauthenticated loads)
- Dismissible amber banner added to root layout (non-blocking, above content)
- Tested via debug menu trigger; banner fires and clears correctly
state_referenced_locally warnings in ae_idaa_comp__event_obj_id_edit_v2.svelte:
- lu_country_list and lu_country_subdivision_list $state runes were read in
top-level synchronous if/else blocks; moved into onMount
- Add onMount to Svelte imports
- Remove unused .field-richtext CSS selector
Remaining 32 warnings in 2 files are either:
- CSS @apply / @reference warnings from the CSS language service not understanding
Tailwind v4 at-rules (harmless, build works fine)
- Warnings in the legacy v1 edit form (no code references it)
Two state_referenced_locally warnings on data prop in
recovery_meetings/[event_id]/+page.svelte: reading a $props()
rune synchronously in a top-level if (browser) block only captures
the initial value.
Move the postMessage block into onMount (browser-only by nature);
remove the now-redundant 'browser' import.
- Update stale comment in menu_location_list.svelte: prop is already
$bindable(null), comment incorrectly said it was not
- Confirm cleanup_tmp_files is wired in launcher_background_sync.svelte
- Mark both items done in TODO__Agents.md
Rollup was splitting svelte/src/internal/client/runtime.js into a
different chunk from svelte/src/index-client.js, producing ~35 warnings
about untrack/tick re-exports leading to broken execution order.
Add manualChunks function to vite.config.ts to colocate all svelte
node_modules into a single 'svelte-vendor' chunk, keeping runtime.js
and index-client.js together.
- Remove vestigial try_cache param from generate_qr_code (never used in body)
- Remove vestigial try_cache from ae_core_functions: load_ae_obj_id__site_domain,
update_ae_obj_id_crud, update_ae_obj_id_crud_v2 (none referenced it in body)
- Add proper SWR pattern to load_ae_obj_id__sponsorship_cfg and
load_ae_obj_id__sponsorship; change defaults from false to true
- Change load_ae_obj_id__event_file default try_cache from false to true
(consistent with load_ae_obj_li__event_file)
- Change load_ae_obj_id__hosted_file default try_cache from false to true
(consistent with load_ae_obj_li__hosted_file)
- Remove stale try_cache arg from element_ae_crud.svelte caller
element_manage_event_file_li_all.svelte — also derives context_session_type_code
via Dexie chain (event_presentation → session, or event_presenter → presentation →
session) and passes it to element_manage_event_file_li. Fixes the button not showing
when viewing a presenter's files from the session view.
element_manage_event_file_li_direct.svelte — extends the Dexie chain to also handle
event_session (direct lookup) and event_presentation, not just event_presenter.
Both: correct API URL to /v3/hosted_file/ per backend agent's examples.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The endpoint is registered under the older hosted_file router at /hosted_file/{id}/convert_file,
not under the v3 actions router. Both list and table convert buttons were sending to the wrong path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v_event_file joins event_session only via event_file.event_session_id.
Files with for_type='event_presenter' have event_session_id=NULL on the
file record itself, so event_session_type_code is structurally always NULL
from the API for these files — no amount of refreshing can fix it.
Instead of relying on the file's event_session_type_code, derive the session
type in element_manage_event_file_li_direct via the Dexie chain:
presenter.event_presentation_id → presentation.event_session_id → session.type_code
Pass the result as context_session_type_code to element_manage_event_file_li,
which now checks EITHER the file's own event_session_type_code OR the context
prop against 'poster' to show the PDF→Image convert button.
Sessions are guaranteed in Dexie because the pres_mgmt layout loads
inc_session_li:true on every navigation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
onMount fired before the parent presenter liveQuery resolved, so
link_to_id was undefined and the refresh was silently skipped.
Using \$effect makes the background refresh re-run once link_to_id
becomes available (after the presenter Dexie lookup completes),
ensuring event_session_type_code is written to Dexie and the
PDF→image convert button renders correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The presenter detail page loads files with try_cache:false, which fetches
fresh data from the API but does NOT write it to Dexie (by design in the
SWR implementation). The file list's liveQuery then reads stale Dexie
records that lack event_session_type_code, causing the PDF→image convert
button condition to silently fail for presenter files in poster sessions.
Fix: trigger a try_cache:true background refresh on mount in the direct
wrapper so fresh API data (with event_session_type_code='poster') is
persisted to Dexie and the liveQuery re-renders with the correct field.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the convert button added to the table view (ae_comp__event_file_obj_tbl).
The list view (element_manage_event_file_li) is the primary Pres Mgmt UI
for managing event files per object (session, presenter, location, etc.).
Same conditions: edit_mode on, extension=pdf, event_session_type_code=poster.
Per-row status: idle → converting → done | error with retry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a per-row "Convert PDF → Image" button in ae_comp__event_file_obj_tbl.
Only shown when edit_mode is on, the file is a PDF, and the session
type_code is 'poster' — poster sessions need images in the Launcher modal
(which uses <img>, not a PDF viewer).
Calls GET /v3/action/hosted_file/{id}/convert_file (pdf2image, 3840px wide,
first page, saves as a new hosted_file linked to the same parent object).
Per-row status tracking: idle → converting → done | error with retry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Persistent stores grow and change over time. svelte-persisted-store deep-merges
old localStorage values with new defaults, so stale values (e.g. hash_prefix_length: 1)
silently survive schema changes and cause subtle bugs.
- src/lib/stores/store_versions.ts:
Single source of truth for AE_LOC_VERSION / AE_EVENTS_LOC_VERSION.
Side-effect on import: reads raw localStorage and wipes if __version mismatches.
Must be imported first in ae_stores.ts and ae_events_stores.ts so the wipe
happens before persisted() hydrates from localStorage.
- ae_stores.ts + ae_events_stores.ts:
Import store_versions as first import; add __version to persisted store defaults.
- documentation/TODO__Agents.md:
Added stores refactor task — both store files need a cleanup pass.
Bump AE_LOC_VERSION or AE_EVENTS_LOC_VERSION by 1 on breaking schema changes.
Non-breaking changes (new optional fields, default value tweaks) do not need a bump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- launcher/+layout.svelte: dead {:else if $events_slct.event_session_id} branch
(condition identical to preceding {#if}) replaced with correct
{:else if $events_slct.event_location_id} — shows "Select a session"
prompt when a room is chosen but no session is yet selected.
- launcher/[event_location_id]/+page.svelte: removed unreachable
handle_get_device_info() function (never called; pre-relay pattern
superseded by launcher_background_sync.svelte's run_device_heartbeat()).
Cleaned up now-unused imports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove tmp_shell_handlers.ts from SvelteKit repo — this was a draft of the
Electron main-process shell handler that was placed in the wrong repo. It used
ipcMain (Electron main-process only) and could never run in SvelteKit. Was not
imported anywhere but was misleading.
- Fix src/lib/electron/README.md:
- Correct broken doc link (was AE_EVENTS_LAUNCHER_NATIVE_INTEGRATION.md,
actual filename is PROJECT__AE_Events_Launcher_Native_integration.md)
- Mark electron_native.js as DEPRECATED — do not import (was described as
active "Bridge Logic", which was incorrect and confusion-causing)
- Add clear bridge architecture diagram showing the three-layer flow
- Note that aether_app_native_electron/ is the active Electron repo
- Update PROJECT__AE_Events_Launcher_Native_integration.md:
- Fix Layer 1 file path: aether_app_native/ → aether_app_native_electron/
- Section 5.3: Phase 5 actuators are all implemented, not "planned" — update
recording, display layout, power control, window control, wallpaper to reflect
actual implementation status; note update_app is a stub
- Section 7: Expand IPC whitelist to cover all relay functions including new
cleanup_tmp_files, system handlers, and full parameter signatures
- Document get_seed_config / get_jwt as intentionally not relayed to UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The event_device table has no hash_prefix_length column, so incoming_dev.hash_prefix_length
is always undefined from the API. The old merge logic preserved whatever was in localStorage,
which had a stale value of 1 from earlier testing — causing the background file sync to create
1-char cache subdirectories instead of the correct 2-char SHA-256 prefix dirs.
Background sync now consistently creates 2-char dirs matching launch_from_cache behavior.
If the API ever returns a hash_prefix_length, it will be used; otherwise the floor is 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- launcher_cfg_local_actions.svelte: add "Cache Maintenance" block
(native-only, gated by is_native && cache_root); number input for
max age in hours, "Clean Now" button with status feedback
- launcher_background_sync.svelte: read cleanup_tmp_max_age_hours
from events_loc.launcher store (default 24h) instead of hardcoded
1440 minutes; setting persists across sessions via persisted store
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- manifest.webmanifest/+server.ts: swap PUBLIC_AE_API_SECRET_KEY →
PUBLIC_AE_BOOTSTRAP_KEY (least privilege; endpoint only needs a
site-domain lookup, same as the bootstrap use case)
- electron_relay.ts: add cleanup_tmp_files() — runs `find ... -name
"*.tmp" -mmin +N -delete` via native run_cmd bridge
- launcher_background_sync.svelte: call cleanup_tmp_files() on mount
when is_native && cache_root are present (once per startup)
- AE__Permissions_and_Security.md: close Sev-1 audit language
- TODO__Agents.md: mark PUBLIC_AE_API_SECRET_KEY audit as complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New: menu_launcher_controls.svelte — bottom control bar for the launcher
sidebar with font size cycler (A / A+ / A−) and light/dark mode toggle,
always visible regardless of access level; visibility toggles (All Files /
All Sessions) moved here from launcher_menu.svelte and restyled to
preset-tonal-tertiary with descriptive title attributes
- launcher_menu.svelte — replace inline visibility-toggle block with
<Menu_launcher_controls />; add full header doc-comment describing
component structure and data flow
- menu_location_list.svelte — add header doc-comment covering purpose,
visibility rules, data flow, and the intentional non-bindable prop pattern
- documentation/TODO__Agents.md — mark font size cycler task complete
- RUN --mount=type=cache,target=/root/.npm for both npm install steps
Persists npm cache across builds so unchanged deps aren't re-downloaded
- deploy:staging now targets ae_app only (skip api/nginx rebuild)
- compose:down includes --profile database to remove mariadb/pma containers
and cleanly release ae_dev_net (fixes 'Resource is still in use' warning)
PUBLIC_AE_BOOTSTRAP_KEY replaces the hardcoded 'IDF68Em5X4HTZlswRNgepQ' in:
- src/routes/+layout.ts (site-domain bootstrap request)
- src/routes/testing/+page.svelte (trace agent key)
Added to .env.staging, .env.prod, .env.local (gitignored), and updated
.env.staging.default / .env.prod.default with XXXX placeholders.
Key can now be rotated independently from the main API secret key.
- core__crud_generic.ts: guard patch result logs (lines 246/252) with
if (log_lvl) — these fired on every successful patch call
- e_app_sign_in_out.svelte: already committed in previous round
- element_manage_hosted_file_li.svelte: already committed in previous round
All other console.log calls in launcher/lib files confirmed already guarded
via $B2 context check. Remaining unguarded logs are in event handlers
(fire on user action only, not hot render paths) or testing/admin pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- launcher/+layout.svelte: convert lq__event_session_obj from $derived to
$derived.by() so Svelte tracks event_session_id as a dependency; the old
pattern read the store inside the Dexie async callback where Svelte's
tracking is off, so the liveQuery never updated on session change
- ae_events__event_file.ts: fix hardcoded log_lvl: 2 in SWR fire-and-forget
background refresh (always-on debug logging on every cache hit) → 0
- e_app_sign_in_out.svelte: lower 6 call-site log levels (1×log_lvl:2,
5×log_lvl:1) to 0; sign-in runs on every page load
- element_manage_hosted_file_li.svelte: log_lvl:2 → 0 in refresh call;
remove log_lvl=1 assignment + debug block inside click handler; log_lvl:1
→ 0 in delete call
- AE__Performance_Guidelines.md: add 5 Svelte 5 runes rules covering
$derived.by() for reactive liveQuery, liveQuery purity, cheap equality
guards ($id+updated_on, ID-join, shallow_equal), untrack() requirement,
and log_lvl discipline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TODO__Agents.md: Mark QR code on badge front as done — ae_comp__badge_obj_view.svelte
already generates the QR via core_func.js_generate_qr_code() and renders it
inside a {#await qr_data_url} block on the badge face.
PROJECT__AE_combined_front_back_Docker.md: New reference document covering
the combined front+back Docker orchestration architecture (consolidated
notes from the session).
Set log_lvl to 0 in all pages and layouts that had it left at 1 or 2
from development. Also remove two hardcoded `log_lvl = 2` overrides
inside function bodies in reports_files.svelte and
reports_presenters.svelte that were forcing verbose output regardless
of the module-level setting.
Affected: launcher location page, leads pages (2), pres_mgmt reports (2),
presenter +page.ts, IDAA layouts (2), IDAA archives, IDAA recovery
meetings page, journals pages (2).
Multiple failures caused by the SWR background fetch pattern and
collapsed UI sections:
1. IDB settle wait: add waitForFunction that polls IndexedDB for
tmp_sort_1 on the event record. tmp_sort_1 is only written by
_process_generic_props, so its presence signals the background
API fetch has completed and no further liveQuery re-fires will
overwrite form inputs the test is about to fill.
2. JSON.parse for _json fields: update_ae_obj_v3 auto-serializes any
key ending in _json to a JSON string before sending the PATCH.
Tests now parse location_address_json, attend_json, and
contact_li_json from the captured body before asserting field values.
3. Contact 2 section: collapsed by default when contact_2.full_name
and email are null. Collapsed state renders type='hidden' inputs
which Playwright's fill() rejects. Add click on the 'Contact 2
(Optional)' toggle before filling those fields.
4. Admin Options section: same collapse pattern. Add click on the
'Admin Options' toggle before filling status/sort/group/hide fields.
5. Increase suite timeout to 60 s: open_edit_form awaits real lookup
API responses (pass_through_lookups=true) which can take 20-25 s
on slow network, leaving no margin at the default 30 s limit.
The Zoom button onclick initialises attend_json.zoom. However the
background SWR list fetch (load_ae_obj_li__event in +layout.ts) can
overwrite $idaa_slct.event_obj with a fresh IDB record where
attend_json = {} (no zoom key), even if the Zoom button was already
clicked.
Without the guard, the $effect that rebuilds the Zoom full URL and the
template bindings below the Zoom fields access attend_json.zoom.passcode_enc
on an undefined object, throwing a TypeError and crashing the component.
Fix: add `&& $idaa_slct.event_obj.attend_json?.zoom` guard to both the
$effect condition and the {#if} block that renders the Zoom input fields.
Replace $derived(liveQuery(...)) with $derived.by(() => { const id = ...; return liveQuery(...) })
for lq__event_presenter_obj and lq__auth__event_presenter_obj in the
session page.
$derived.by() captures only the specific ID at derivation time, so
unrelated store changes do not recreate the observable. Plain $derived
closes over the entire $events_slct store object, causing unnecessary
observable recreation on any store mutation.
ae_events__event.ts: Add legacy flat address fields (address_name,
address_line_1, address_city, etc.) to properties_to_save. Events
predating location_address_json still return these flat fields from
the API; they must survive an IDB round-trip without being stripped
by _process_generic_props, or the edit form's fallback reads return
undefined.
launcher_background_sync.svelte:
- Move file_sync interval into loop_info $state alongside other
intervals; load from cfg/device config with fallback (30 s). Keeps
all intervals in one place for UI display consistency.
- Align onMount fallback values to match loop_info defaults.
- Add sync_paused mid-loop break: long sync cycles now honour a pause
request per-iteration rather than waiting for the entire batch.
JSON.stringify on large store objects (ae_loc, ae_api, slct, event lists)
was running on every navigation, comparing serialized strings of potentially
deep objects. Replace with targeted comparators:
- Root +layout.svelte: add shallow_equal() helper — O(n keys) key-by-key
identity check instead of O(serialized bytes). Used for ae_api, ae_loc,
and slct sync guards.
- Launcher +layout.svelte: ID-list join-compare for event_location_obj_li
and id_li__event_location (O(n) string vs O(n*m) stringify). Refactor
liveQuery closures to be pure (data-only, no store reads/writes inside
the async Dexie context where Svelte reactivity tracking is undefined).
Move store sync into separate $effects that compare updated_on + id
(O(1)) or ID-join (O(n)) rather than full object serialization.
Fixes a build warning about missing .svelte-kit/tsconfig.json that
appears when building inside Docker. Running npx svelte-kit sync
pre-generates the required SvelteKit scaffolding before the main
build step.
Also update README title to reflect Svelte v5 and trim a trailing
whitespace in the environment handling section.
- menu_location_list.svelte: mark slct_event_location_id as $bindable(null) to
resolve Svelte 5 compiler warning (bind:value used on non-bindable prop)
- TODO__Agents.md: audit and close resolved launcher items:
- Location select auto-load bug: fixed via $derived.by() liveQuery pattern
- Session Search button visibility: was never a real bug, hardcoded false
- Dark mode select fix: already applied via app.css color-scheme rules
Add html.dark/html.light color-scheme rules to app.css so native controls
(select dropdowns, scrollbars, date pickers) follow the app's class-based
dark mode rather than the OS theme.
- Replaced manual rsync/npm_deploy workflow with multi-stage Docker builds.
- Added Dockerfile and .dockerignore for staging and production environments.
- Added 'deploy:staging' and 'deploy:prod' scripts to package.json.
- Updated README.md with new deployment instructions.
Three new Firefly-family themes following the AE_Firefly design system:
- AE_Firefly_SteelBlue: metallic steel blue primary (~214°), burnished gold
secondary, cobalt navy tertiary, chrome silver surfaces
- AE_Firefly_Indigo: deep indigo primary (~266°), violet secondary, dusty
rose tertiary, velvet slate surfaces
- AE_Firefly_Rainbow: coral-red primary (~15°), emerald green secondary,
rich violet tertiary, sunrise cream surfaces (spans the visible spectrum)
All variants share consistent semantic colors (success/warning/error) with
AE_Firefly for cross-theme recognizability. All WCAG 2.1 AA compliant.
Also adds URL param support for theme switching:
- ?theme=AE_Firefly_SteelBlue&theme_mode=dark
- Params applied to ae_loc (persisted), then silently removed via replaceState
Replace the old float-right Show/Hide toggle button + separate collapsible
div with an inline caret-button heading inside a consistent section card
(bg-surface-100-900, border-error-400) — matching the recovery meetings v2
form style. All four forms updated: Post, Post Comment, Archive, Archive
Content. Existing class:hidden behaviour preserved so FormData is unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin Options are rarely changed; collapsing by default reduces visual
noise on the long edit form. Hidden inputs preserve status/enable/hide/
priority/sort/group values when the section is collapsed so a save never
silently resets admin fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Save Changes button at top of form so users don't scroll to the
bottom to save; existing meetings only (mirrors v1 behaviour)
- Collapse Contact 2 subsection behind a toggle; auto-expands when the
meeting already has Contact 2 data saved
- Add hidden inputs when Contact 2 is collapsed so FormData preserves
existing contact info rather than overwriting with nulls on save
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full payload-verification test suite for ae_idaa_comp__event_obj_id_edit_v2.
Root cause fixed: $ae_loc.lu_time_zone_list empty at mount caused Svelte 5 to
render <input type=text name=timezone required value=''> instead of the <select>
branch. HTML5 required validation silently cancelled onsubmit with no JS error
and zero network activity — waitForRequest timed out with no obvious cause.
Fix: pre-seed lu_time_zone_list in addInitScript so the <select> branch renders
on first mount with a valid value already set.
Key patterns established:
- setup_idaa_auth(): pre-seeds ae_loc + ae_idaa_loc in localStorage via
addInitScript; includes lu_time_zone_list and window.__ae_test_mode = true
- setup_api_mocks(): selective pass-through flags for lookups and site_domain
- open_edit_form(): waitForFunction guards for name field, country lists, and
the timezone required field before any interaction
- capture_patch_body(): registers waitForRequest before click, awaits after
README.md updated with deep-dive section covering:
- HTML5 form validation silent block and how to diagnose it
- Svelte 5 one-time value= bind trap
- addInitScript store pre-seeding pattern
- __ae_test_mode email suppression
- waitForFunction patterns for reactive state
- Route mock strategy (pass-through vs fixture)
Add a guard at the top of send_email() that checks globalThis.__ae_test_mode.
If truthy, logs a suppression message and returns null immediately so no HTTP
request is made. This prevents real emails being sent when Playwright tests
exercise components that call send_staff_notification_email() after a successful
save. Activate by setting window.__ae_test_mode = true in addInitScript.
- [post_id]/+page.ts: remove await on post load (same pattern as archives);
liveQuery renders immediately, replace error(404) with console.warn.
- ae_idaa_comp__post_options: add in-flight creating guard + spinner on Create
New Post button to prevent double-submit; remove ~15 lines of dead commented
code left over from previous refactors.
- ae_idaa_comp__post_obj_id_view: remove confirm() dialog before opening the
Add Comment form — no need to confirm an intent to type a comment.
- ae_idaa_comp__post_comment_obj_id_edit: remove redundant block that manually
injected post_id into the payload for new comments; post_id is already
handled correctly by the form payload builder.
- +page.ts: remove await on archive load so navigation is not blocked;
liveQuery renders from Dexie cache immediately, API result updates reactively.
Replace error(404) with console.warn — soft failure is correct for IDAA.
- ae_idaa_comp__archive_obj_li: add Create New Archive button (trusted+edit_mode
only) with in-flight spinner and creating guard to prevent double-submit.
Layout adjusted to justify-between to accommodate the new button.
- ae_idaa_comp__archive_content_obj_li: add Create New Archive Content button
with same spinner/guard pattern; pre-populates original_timezone from parent
archive so staff do not need to re-select it for every content item.
Two bugs fixed:
1. scroll_to handler scrolled to page top (0,0) instead of the iframe's
position in the Novi page. The iframe sits below Novi's own header/nav,
so the user ended up looking at the Novi header instead of the iframe
content after navigation. Fixed to use getBoundingClientRect() to scroll
to 20px above the iframe's actual document position. Also added the
missing scroll_to handler to idaa_novi_iframe_archives.html (it had none).
2. Parent URL not updating with event_id/post_id/archive_id on navigation.
Detail pages sent postMessage using $idaa_slct.<id> (the store), which
is still null at synchronous init time — the $effect that populates it
runs later. Fixed to read from data[data.account_id].slct.<id> directly
(set by the +page.ts load function from URL params before render).
Also added afterNavigate to idaa/+layout.svelte to send scroll_to on all
client-side navigations, covering cases the per-page blocks miss (e.g.
navigating back to the list view).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, IDAA iframe access relied on trusting URL params (uuid, email,
full_name) passed from Novi — any 36-char string granted authenticated access
with no actual verification.
The (idaa)/+layout.svelte now performs an async Novi API call on every UUID
load to verify the UUID exists, fetches name/email directly from Novi (cannot
be spoofed via URL), and sets $idaa_loc.novi_verified on success.
All-or-nothing: if novi_idaa_api_key is absent or the call fails, access denied.
- ae_idaa_stores.ts: add novi_verified boolean field to idaa_loc
- (idaa)/+layout.svelte: async UUID verification with spinner to prevent
Access Denied flash; permission upgrade-only strategy preserved
- video_conferences/+page.svelte: skip duplicate Novi member details call if
layout already verified ($idaa_loc.novi_verified check)
- iframe HTML files: remove browser-side Novi API fetch and email/full_name
params; pass only uuid; add README/START/STOP/WARNING comments for client
staff; fix iframe-before-script DOM ordering bug
- documentation: CLIENT__IDAA_and_customized_mods.md updated with full
verification flow, site_cfg_json fields, permission table, access gate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
V3 CRUD returns 'id' as the random identifier, not 'person_id_random'.
The person check and assignment were using the old field name, causing
the 'no person record' alert even when the lookup returned valid data.
Now checks person_rec.id ?? person_rec.person_id_random as a fallback
for backwards compatibility.
- Check user_response?.detail (FastAPI standard) before user_response?.error
- Distinguish null response (network/server error) from bad credentials
- Remove silent console.error-only path; user now always sees a message
- Fix misleading 'auth_ae_obj__username_password' label in user_id+key flow
- Clarify 'no person record' message to suggest contacting administrator
- Simplify success log messages (remove dead commented-out code)
The /user/authenticate endpoint returns 'user_id' not 'user_id_random'.
Both auth flows (user_id+auth_key and username+password) were checking
user_response?.user_id_random, which was always undefined, causing the
user_id to never be set and falling through to the email lookup fallback.
Fixed both .then() handlers to check user_response?.user_id and assign
user_obj.user_id.
The three liveQueries that depend on $events_slct.event_location_id were
plain liveQuery() calls, not $derived.by(() => liveQuery(...)). This meant
Svelte store changes did NOT cause them to re-run.
Root cause of the 'hung' bug:
- On initial load at /launcher (no location in URL), id = null
- Dexie watches the event_location_id = null index range
- User selects a location (or navigates to /launcher/{id}): store updates
- Sessions for the real location are in a DIFFERENT Dexie range
- Dexie never fires because the null range was never touched
- If sessions are already in IndexedDB cache (no new DB write), the list
stays permanently frozen at []
Fix: convert lq__event_session_obj_li, lq__location_event_file_obj_li, and
lq__event_location_obj to $derived.by(() => liveQuery(...)). When
event_location_id changes, $derived.by creates a new Observable targeting
the correct location, which immediately queries Dexie for existing cached
data and then watches that range for further changes.
Also: remove the .reverse() before .sortBy('name') on the session query —
.sortBy() always re-sorts so .reverse() before it was a no-op.
Onsite operators may have the sys menu locked/unavailable. This button
in the always-visible launcher footer gives them direct A / A+ / A−
control that cycles $ae_loc.font_size_mode, which the root layout DOM
effect picks up and applies as html.font-size-* class.
- event_page_menu: set events__session_search=false — the Session Search nav
link was redundantly appearing on the Session Search page itself
- element_manage_event_file_li: replace hardcoded gray hover colors with
theme-aware surface tokens (hover:bg-surface-100-900, border-surface-200-800)
and add transition-colors; fixes light-on-light in dark mode for the file
list table rows and Event File Purpose select element
- font size cycler (default → larger → smaller → default):
- ae_stores: add font_size_mode: 'default' to ae_loc defaults
- app.css: html.font-size-larger (112.5%) and html.font-size-smaller (87.5%)
- +layout.svelte: DOM effect applies/removes font-size-* class on <html>
- e_app_sys_menu: compact A / A+ / A− button cycles the mode
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.
- Fix site_domain mock to return array { data: [mock_site_domain] } so
ae_api.headers['x-account-id'] gets a valid 11-char account_id.
Previously returned { data: {} } causing layout to fall back to 'ghost'
(5 chars) and the real API PATCH rejected the request with 422.
- Add integration test describe block (Real Backend Save) with
pass_through_event_patch option to let PATCH reach the real API.
- Extract localStorage injection into setup_idaa_auth() helper.
- Fix test name: 'sends PUT' → 'sends PATCH' (the API uses PATCH).
- All 14 tests pass.
13 tests covering: form render, form sections, field names/types,
weekday checkboxes, timing inputs, contact fields, address fieldset
visibility, virtual checkbox, text input, and PATCH API submission.
All tests pass (13/13). Fully mocked — no real backend required.
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>
- PROJECT doc: Task 2 status → complete (v1); added implementation
details, default px values table, and note on future mm/inch iteration
- PROJECT doc: noted the review page field list bug fix (commit 011fc19a)
- MODULE doc: added "Recently Completed" section above Still Needed;
cleared Badge Review Form and Print Font Controls from HIGH PRIORITY list
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>
- CLIENT__IDAA_and_customized_mods.md: New comprehensive doc covering IDAA
architecture, all 4 submodules (Archives, BB, Recovery Meetings, Jitsi),
Novi UUID auth system, permission levels, state stores, iframe integration,
and testing requirements. Reverse-engineered from source 2026-02-26.
- MODULE__AE_Events_Badges.md: trailing whitespace only
- tests/README.md: blank line only
Describes which object types are always-flat (never nested in URL) for
ALL operations, vs event sub-objects which use nested paths for mutations
but flat paths for all reads (GET, list, search, delete).
Always-flat objects:
- Core: account, activity_log, address, contact, hosted_file, organization,
page, person, site, user
- Other: archive, event, journal, post
Event sub-objects (event_badge, event_session, etc.) use nested
create_nested_obj_v3 / update_nested_obj_v3 for POST/PATCH, but flat
paths for everything else.
Includes Playwright mock URL patterns for each operation type.
- AE__Architecture.md: Add section 7 -- Runtime Environment: Browser vs Electron.
Electron is ONLY for Events Pres Mgmt Launcher. Badge printing (and everything
else) works via standard browser window.print(), no Electron needed.
- MODULE__AE_Events_Badges.md rev 3: Update print section to browser-first approach,
remove Electron from print workflow, add two new test lessons (flat API URLs,
CSS attribute vs DOM property), mark all tests passing.
- TODO__Agents.md: Add completed data integrity test fixes item, add window.print()
wiring to upcoming tasks, expand input field audit note.
- Badge search mock: was checking nested URL /v3/crud/event/{id}/event_badge/search
but search_ae_obj_v3 uses flat path /v3/crud/event_badge/search
- Template list mock: was checking nested path, fixed to flat /v3/crud/event_badge_template/
with for_obj_id query param (matches get_ae_obj_li_v3 behavior)
- Badge objects: add _random ID fields (event_badge_id_random, id_random, event_id_random)
required for Dexie IDB processing
- Template edit assertion: input[value*=...] CSS checks HTML attribute not DOM property;
Svelte bind:value sets DOM property only — fix to use getByLabel('Template Name')
- Relax null body check: the debug panel footer can contain 'null' legitimately
- 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.
- Simulates complete check-in: navigate → search → view → edit → print → return
- Mocks V3 API for event, badge search, and PATCH operations
- Tests override field editing (professional_title_override)
- Documents future attendee review feature (email link workflow)
**STATUS: WIP** - Search results not displaying in test environment
- API mocks configured correctly
- IDB cleared properly, schema managed by app
- Store initialization includes qry__remote_first flag
- Issue: Badge list not rendering after search (timing or store issue)
- Screenshot saved to test-results/ for debugging
- Added testing_site_id, testing_site_domain_id, testing_fqdn, testing_person_id
- Created mock_site_domain object matching +layout.ts expectations
- Updated minimal_v3_mocks.ts to use mock_site_domain
- Fixes 'Domain Not Registered' overlay in Playwright tests
- All test IDs now documented with comments from tests/README.md
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
Project-specific context file for Claude Code containing:
- Critical privacy/business rules (IDAA, Journals)
- Mandatory workflows and tech stack
- API patterns and key documentation references
- Active issues tracking
Complements global ~/.claude/CLAUDE.md with SvelteKit-specific guidance.
New test validates that presentations AND presenters render on first
navigation when IndexedDB is empty (cold-start scenario).
Test Configuration:
- Demo Session: DOW3h7v6H42 (703) 'How To Do Things'
- Demo Presentation: 7U2eXSjR6H4 (1670) 'Build a House'
- Demo Presenter: gT-hxnifb-0 (2202) 'Bob The Builder'
Tests:
1. UI visibility: Session name, presentation, and presenter all visible
2. IDB integrity: Verifies all nested data written to IndexedDB
Both tests passing - confirms fix works correctly.
- Updated Dexie/liveQuery guide with detailed explanation of the
try_cache + microtask yield bug pattern
- Marked session view refactor project as RESOLVED (2026-02-26)
- Added inline code comments to all three fixed loader functions
explaining the critical fixes
Documents the 'refresh twice' bug resolution for future reference.
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.
Adds the first successful, stable Playwright integration test for the SvelteKit frontend. This test serves as a template for future UI/UX and integration tests.
Key outcomes and learnings captured in this test:
- Establishes a pattern for running tests against the high-speed 'npm run dev' server.
- Verifies critical API security header logic, including the unauthenticated bypass for lookups and account ID scavenging from localStorage.
- Solves application boot crashes in a test environment by injecting a complete, default 'ae_loc' state object into localStorage before the app hydrates.
- Demonstrates the correct, race-condition-free pattern for waiting on network requests that are handled by API mocks.
This commit also removes the old, unreliable Node.js-based scripts ('verify_jwt_logic.js', 'verify_jwt_sync.js') that these new tests replace.
- Sequenced sections as Country, Subdivisions, then Time Zones.
- Implemented a reactive toggle for 'only_priority' in the Time Zone section to support high-priority filtering.
- Switched to a full-width card layout for improved readability of large datasets.
- Updated V3 lookup API and core timezone loader to support the 'only_priority' flag.
- Enabled high-priority timezone filtering in IDAA Recovery Meeting and Archive editors to streamline selection lists.
- Created 'src/lib/ae_api/api_get__lookup_v3.ts' to handle the new '/v3/lookup/{lu_type}/list' endpoint.
- Refactored 'get_ae_obj_li_for_lu' in 'api.ts' to prioritize the V3 system for countries, subdivisions, and time zones, with V2 fallback for legacy types.
- Ensured lookups use the 'x-no-account-id' bypass for unauthenticated site bootstrapping.
- Updated core country, subdivision, and time zone loaders to use the refined API interface.
Implemented explicit 'enabled' and 'hidden' parameter support in the Event data layer ('load_ae_obj_id__event').
Updated the Events Launcher layout and background sync engine to proactively fetch all enabled locations (including hidden ones), ensuring the room select list is complete and stays updated.
Refined 'launcher_file_cont.svelte' to only display native-specific file opening instructions when 'app_mode' is 'native'.
Updated AGENT_TODO.md to reflect task completion and new priorities.
Updated the Event File data layer to support the 'inc_hosted_file' flag in load and search functions, enabling on-demand retrieval of joined Hosted File metadata.
Refined the data mapping in 'process_ae_obj__event_file_props' to include 'content_type' and strictly controlled which properties are persisted to IndexedDB, adhering to the 'Bite-Sized Data' principle by excluding unneeded backend fields like subdirectory paths.
Enhanced 'get_ae_obj_li_v3' to support generic parameter pass-through for V3 CRUD operations.
Mapped prefixed backend fields 'hosted_file_hash_sha256' and 'hosted_file_size' to flat 'hash_sha256' and 'file_size' properties in the Event File data layer. This resolves component crashes (TypeError: slice on null) by ensuring IndexedDB is populated with valid hash strings.
Updated AGENT_TODO.md to reflect recently completed tasks.
Implemented Svelte 5 callback props (onsuccess, oncancel) for Badge create and upload forms, replacing legacy dispatchers.
Updated the AE Field Editor to accept an optional 'id' prop, resolving property mismatch errors.
Updated the Event Settings page to use the new callback prop interface, clearing type assignment errors reported by 'npm run check'.
Standardized access level hierarchy (super > manager > administrator > trusted) and added hierarchical comparison utilities to 'ae_util'.
Refactored IDAA layout to use an 'Upgrade-Only' permission strategy, preventing context-specific identifications from downgrading global Manager privileges.
Implemented strict gated filtering in the Journal Entry list: hidden and disabled items now correctly require both the appropriate hierarchical role (Trusted/Admin) AND active Edit Mode.
Added 'activity_log' table to Dexie 'ae_core_db' (v4) to support local caching of tracking data.
Implemented 'process_ae_obj__activity_log_props' with robust timestamp fallbacks.
Refactored 'qry__jitsi_report' to follow the Frontier module pattern, ensuring consistent V3 search and local cache synchronization.
Implemented 'Meeting Name (A-Z)' and 'Meeting Name (Z-A)' sorting for Recovery Meetings, including UI dropdown updates and client-side re-sorting logic.
Updated documentation/GUIDE__DEVELOPMENT.md to v1.1 with clarified verification steps and inter-agent coordination protocols.
Minor label cleanup in Journal editor.
Updated '_process_generic_props' in multiple libraries to ensure 'updated' timestamp always falls back to 'created_on' or epoch start, preventing null values from breaking newest-first ordering in IndexedDB.
Aligned Recovery Meetings and Archives list views to use these pre-computed sort keys for consistent UI behavior even when 'updated_on' is null.
Implemented critical security and architectural fixes to align the frontend with the Aether API V3 standard and resolve 403 Forbidden race conditions.
- Unified CRUD Helpers: Updated get, create, update, and delete helpers to use the standard /v3/crud/{obj_type}/{id} paths, ensuring correct backend isolation context.
- Auth Scavenging: Implemented direct localStorage scavenging for 'x-account-id' in core fetch helpers to prevent hydration race conditions in Svelte 5.
- Header Cleanup: Purged redundant 'x-aether-api-token' and fixed misplaced protocol headers in global stores.
- Reliability: Fixed 'Content-Type' typos and standardized kebab-case header normalization.
- Created consolidated AE_Obj_Field_Editor_V3 component using Svelte 5 Runes.
- Standardized on V3 CRUD API (PATCH /v3/crud/{obj_type}/{obj_id}).
- Implemented local state guards to prevent reactivity loops.
- Added support for multiple field types (text, textarea, select, checkbox, tiptap).
- Created a dedicated testing playground with real Demo account data.
- Updated the PROJECT_AE_OBJECT_FIELD_EDITOR_V3_UPGRADE.md plan.
Aligned the Events Badges, Templates, and IDAA Bulletin Board modules with
Aether UI/UX v3 standards by prioritizing semantic String IDs (e.g., event_id,
badge_id) over legacy '_random' variants in API helpers, Dexie processors,
and UI components.
- Refactored badge and template loaders to use clean ID field names.
- Updated search and LiveQuery logic in badge management views.
- Cleaned up unused imports and legacy code in +layout and +page components.
- Standardized ID mapping in generic object processors.
- Improved IDAA post creation UX with autofocus and empty title defaults.
- Updated 'handle_open_file' in launcher_file_cont.svelte to correctly pass filename to API.
- Fixed WebSocket ae_download command in launcher layout to include filename.
- Implemented a safety net in api_get_object.ts to extract filename from params if missing.
- Added 'download' attribute to Hosted Files download button for direct links.
- Refactored launcher menu components to use Svelte 5 global 'page' state instead of obsolete 'data_url' prop.
- Optimized session navigation by switching from static data_url to reactive $page.url.\n- Fixed selection hang when entering the Launcher without an initial session ID.\n- Synchronized global header and idle logic with derived layout observables.\n- Streamlined prop passing to ensure clean state transitions during session switching.
- Optimized background API refreshes to focus on the selected session instead of the entire room.\n- Eliminated redundant bulk database writes that were causing UI flickering during refresh cycles.\n- Staggered initial data fetches to prevent API request storms.\n- Added detailed logging for easier tracing of background sync operations.
- Standardized default view to 'alt' for session loading to ensure persistent file counts.\n- Hardened 'No files' warning logic to prevent false positives during background refreshes.\n- Restored reactive session observable in layout for global header and idle logic.\n- Documented overwrite pitfalls in SVELTE_DEXIE_GUIDE.md.
- Migrated background timers to persistent 'sync_intervals' store.\n- Updated Sync Monitor and Config UI to display and edit all six polling loops.\n- Ensured timer settings are applied and persisted regardless of native mode status.\n- Refined initialization logic to prioritize user configuration with robust fallbacks.
- Added background API refresh loops for room-level presentation and presenter metadata.\n- Updated Sync Monitor UI to display all six loop intervals.\n- Added UI controls in Launcher Config for fine-tuning polling periods.\n- Staggered initial background fetches to prevent API connection flooding.\n- Corrected timer assignments to separate structural metadata from room content.
- Added dedicated background timers for room-level presentation and presenter metadata.\n- Staggered initial data fetches to prevent request storms during room entry.\n- Cleaned up redundant timer assignments and separated API refresh from native file sync.\n- Optimized loop frequencies for better balance between freshness and performance.
- Silenced 'Heartbeat skipped' warnings when not running in native Electron mode.\n- Hardened device ID retrieval using String-Only ID prioritization logic.\n- Improved reliability of background sync cycles during session transitions.
- Moved session observable to child component to ensure reactive header updates.\n- Implemented deferred loading for presenters and files to prevent request storms.\n- Fixed WebSocket navigation paths to use store-backed IDs.\n- Streamlined Svelte 5 component lifecycle for better performance and SWR responsiveness.
- Optimized session list load to be shell-only, preventing initial request storms.\n- Moved deep data fetching (presenters/files) into the Presentation component level using Svelte effects.\n- Deferred child collection lookups until components are rendered in the DOM.\n- Fixed ae_api import in launcher_presentation_view.\n- Hardened background refresh logic to respect requested views.
- Initialized event_session_id to null in store template to prevent Svelte 5 binding errors.\n- Fixed invalid bind expressions in Launcher layout.\n- Corrected view: alt propagation in session list background refresh to ensure file counts are retrieved.\n- Hardened duplicate launch protection with verify_hash enabled by default.\n- Updated SVELTE_DEXIE_GUIDE.md with binding pitfall documentation.
- Implemented SHA-256 integrity checks and stale temp purge in native download logic.\n- Enabled strict hash verification in background sync cycle.\n- Made CPU load gauge dynamic using real-time loadavg metadata.\n- Consolidated native app documentation into single master manual.\n- Marked legacy electron_native.js as deprecated.\n- Updated roadmap with temp cleanup and launch protection tasks.
- Convert decorative labels to spans to fix a11y warnings in Accounts and Sites pages
- Add rel="noopener noreferrer" to external links for security
- Use untrack for Svelte 5 state initialization from props in e_app_sign_in_out.svelte
- Make TipTap editor styles global to fix scoping warnings for dynamic content
- Integrated TipTap rich text editor for Booth Descriptions and Exhibitor Notes.
- Implemented strip_html utility for clean search/preview of rich text fields.
- Renamed exhibit loading functions to follow load_ae_obj_id__event_exhibit* pattern.
- Hardened property processors with 'undefined' string guards and automatic reload triggers.
- Resolved type mismatches and naming inconsistencies across the Leads module.
- Verified zero-error state via svelte-check.
- Hardened 'Comp_exhibit_license_list' to handle both raw JSON and IDB object formats.
- Updated all labels to 'Licensed Leads User' or 'Licensee' for consistency.
- Verified Admin-only restriction for the licensee management section.
- Fixed silent parsing failures that caused empty staff lists.
- Added real-time checking for existing leads in manual search.
- Implemented 'Already Captured' status for QR scanner with direct 'View Lead' link.
- Resolved all remaining TypeScript typing issues in capture components.
- Optimized Dexie lookups for existing lead detection.
- Added 'Comp_lead_detail_form' for editing licensee responses.
- Implemented reactive form generation based on Exhibit question definitions.
- Wired up 'Edit Mode' toggle in Lead Detail page.
- Added CRUD editors for lead notes, priority, and visibility status.
- Refactored Lead List to use direct observable subscription for better reactivity.
- Implemented in-memory 'Hard Guard' filter to ensure licensee selection is strictly enforced.
- Fixed 'TypeError: s.subscribe is not a function' by removing legacy $ prefix from resolved props.
- Resolved TypeScript typing errors in Lead Detail and Search components.
- Migrated Badge Search icons to Lucide.
- Implemented dual-format parsing for 'license_li_json' (handles raw string and IDB objects).
- Added fallback lookup by 'event_exhibit_id_random' for robust record resolution.
- Fixed reactivity for licensee dropdown population.
- Hardened default selection logic based on Aether access levels.
- Renamed all staff-related fields and variables to 'licensee'.
- Implemented correct filtering logic for Aether Admins (default All, hide My).
- Implemented correct filtering for booth users (default My, show colleagues).
- Populated dropdown labels with Full Names from license_li_json.
- Removed 'Shared Passcode' from the Lead List filter.
- Disabled redundant comment loading in BB Post list to eliminate '1+N' API request overhead.
- Fixed missing 'untrack' import in BB Post page.
- Refactored IDAA Archive and BB pages to use 'page.params' for robust Svelte 5 reactivity.
- Finalized root layout hydration guards to prevent infinite store synchronization loops.
- Forced priority archives to the top of the list.
- Implemented descending (DESC) sort by the 'sort' field within groups.
- Added a sort selector to the archive list component.
- Centralized sorting logic in-memory within LiveQuery for immediate reactivity.
- Resolved 'untrack is not defined' ReferenceError in Badge Detail page.
- Transitioned Badge, IDAA Archive, and Journal Entry pages to use page.params for robust reactivity.
- Implemented fallback semantic ID lookup for Badges to handle mixed ID formats in URLs.
- Added proper loading states to detail views to prevent 'No IDB record' flashes during hydration.
- Systematically audited and cleaned up duplicate 'untrack' imports across all Svelte files.
- Refactored layouts to derive account data from stable props instead of reactive stores.
- Wrapped store updates in untrack() with deep equality guards to prevent infinite re-renders.
- Resolved duplicate untrack declarations and missing imports across the project.
- Added fetch safeguards to Element_data_store to prevent redundant API calls.
- Standardized hydration patterns to break circular dependencies during initial load.
- Fixed 'Captured initial value' warnings in 65+ components by implementing
proper sync effects with 'untrack' and derived states.
- Hardened Event Settings JSON editors using a temporary string-buffer pattern
to safely decouple object-based data from CodeMirror's string requirements.
- Resolved strict TypeScript mismatches across core routes (Accounts, Sites, etc.)
and improved property indexing safety in views.
- Patched Flowbite-Svelte Drawer transitions for Svelte 5 compatibility using
prop spreading.
- Added comprehensive safety comments to high-risk reactivity blocks.
- Synchronized 'ae_types.ts' with V3 backend models.
- Simplified Tab 2 interface by replacing segmented control with a single toggle button.
- Updated button labels and icons to indicate the available switch action.
- Implemented sticky tab persistence using local storage via events_loc store.
- Aligned Manual Search form styling with Lead List search for UI consistency.
- Updated tab switching logic to support historical navigation within exhibits.
- Center-aligned and stabilized Add Lead content area.
- Added surgical console logging in '_refresh_file_li_background' to track raw API data vs processed records.
- Refined the ID safety net to only inject missing keys, preventing accidental overwrites of existing relationships.
- Hardened '_process_generic_props' to prevent 'null' random IDs from clobbering clean V3 IDs.
- Restored specific object ID fields to 'properties_to_save' for full IndexedDB synchronization.
- Implemented a safety net in '_refresh_file_li_background' to inject missing 'for_id' and 'for_type' from query context.
- Fixed a bug in '_process_generic_props' where 'null' random IDs could overwrite clean IDs.
- Enabled initial debugging logs for event file processing.
- Updated '_refresh_file_li_background' to manually inject 'for_id', 'for_type', and specific object IDs if they are missing from the API response.
- This ensures robust indexing and retrieval from Dexie even when the V3 backend fails to populate linking fields.
- Verified that these 'fixed' objects are correctly processed and saved to the local cache.
- Reverted 'element_manage_event_file_li_direct.svelte' to use 'for_type' and 'for_id' for cache-aware filtering.
- Enhanced 'element_manage_event_file_li_all.svelte' with a combined filter for specific and generic IDs.
- Verified that ID synchronization in 'process_ae_obj__event_file_props' supports these retrieval patterns.
- Updated 'process_ae_obj__event_file_props' to synchronize generic 'for_id' with specific object IDs (e.g., 'event_presenter_id').
- Standardized 'element_manage_event_file_li_direct.svelte' to use specific ID filtering.
- Fixed missing 'prevent_default' in 'ae_comp__hosted_files_clip_video.svelte'.
- Resolved miscellaneous type and syntax errors identified by svelte-check.
- Updated 'create_event_file_obj_from_hosted_file_async' to use the modern V3 action endpoint.
- Standardized 'prevent_default' helper names in root Event and Archive components.
- Applied batch formatting (printWidth: 80) across the settings and events modules.
- Standardized props and UI using Lucide icons and Element_input_files_tbl.
- Migrated state to Svelte 5 runes ($state, $bindable).
- Updated upload logic to handle sequential processing and event_file creation.
- Improved revalidation logic by clearing Dexie cache before refreshing.
- Corrected native 'event.preventDefault()' calls in form agreement components.
- Applied batch formatting (printWidth: 80) across the pres_mgmt module.
- Applied snake_case helper standardization and formatting to element_input_file, hosted_files_clip_video, and websocket_v2.
- Cleaned up deprecated legacy imports.
- Batch formatted all Recovery Meetings module files using Prettier with printWidth: 80.
- Refactored preventDefault to prevent_default in editor and search components.
- Standardized line breaks for long attribute lists and Svelte tags for better readability.
- Ensured consistent snake_case naming for internal identifiers.
- Batch formatted all Journals module files using Prettier with printWidth: 80.
- Refactored preventDefault to prevent_default across all Svelte components.
- Standardized line breaks for imports and long attribute lists for better readability.
- Ensured consistent snake_case naming for internal identifiers.
- Systematically migrated from *_id_random to clean *_id fields in BB, People, and Posts modules.
- Synchronized Post_Comment interface with account_id for multi-tenant isolation.
- Applied optional chaining and local reactive state to harden async UI initialization.
- Refactored common helpers to follow snake_case naming conventions.
- Resolved various minor stability and logic issues across IDAA Bulletin Board.
- Refactored lq__post_obj_li to perform in-memory descending sort by updated_on/created_on.
- Ensured newest and recently modified posts appear at the top of the list.
- Hardened reactivity for Svelte 5 stability.
- Fixed 'post_id' missing error in comment creation by mapping to 'post_id_random'.
- Resolved infinite request loop in post view via untrack() optimization.
- Hardened all property accesses with optional chaining to prevent TypeErrors.
- Migrated comment editor to local reactive state for Svelte 5 stability.
- Refactored post visibility layer to use derived reactive filtering.
- Standardized ID mapping patterns across all BB components.
- Renamed 'comment' table to 'post_comment' in db_posts.ts.
- Bumped Dexie database version to 2.
- Resolved InvalidTableError: Table post_comment does not exist.
- Standardized ID mapping for local storage consistency.
- Added optional chaining to 'Cancel Edit' button title attributes.
- Resolved persistent TypeError: can't access property 'full_name' on null.
- Ensured total UI stability during new comment initialization.
- Added optional chaining to all post_comment_obj property accesses.
- Resolved TypeError when clicking 'New Comment' button.
- Hardened UI stability for asynchronous data loading.
- Added optional chaining to all post_comment_obj property accesses.
- Resolved TypeError when clicking 'New Comment' button.
- Hardened UI stability for asynchronous data loading.
- Deleted redundant reactive trigger in post view component.
- Removed state-syncing side-effects from liveQuery derivation.
- Standardized log_lvl prop to resolve initialization crash.
- Optimized performance for single post loading.
- Added 'account_id_random' to persistent property list to fix local search isolation.
- Standardized search body and helpers to support mandatory x-account-id headers.
- Refactored LiveQuery to use synchronous dependency tracking ($derived.by) for reliable search updates.
- Broadened server-side search to handle inclusive OR logic on the client, preventing disappearing results.
- Updated task list with IDAA Recovery Meeting testing status.
- Resolved 400 Bad Request by whitelisting 'physical', 'virtual', and 'external_person_id' fields in backend searchable_fields.
- Broadened server-side search by removing restrictive AND filters for location types, moving the inclusive OR logic to the client-side filter layer.
- Hardened handle_search_refresh error handling to clear results on category filter changes (Type, Physical, Virtual) while maintaining flicker protection strictly for text search typing.
- Restored permissive visibility for Trusted users, allowing them to see hidden/disabled items regardless of Edit Mode.
- Optimized plural object loading in Archives and Events modules using concurrent Promise.all.
- Standardized ID handling across the search flow to prevent Dexie lookup failures.
- Finalized well-commented code across IDAA modules to document search strategies and workarounds.
- Hardened 'JournalEntry' and 'Sponsorship' object processors to handle null sort values.
- Disabled aggressive pre-loading of archive content in main archives list to improve performance.
- Hardened object processors in Archives and Posts modules to safely handle null 'sort' values, preventing runtime TypeErrors during data synchronization.
- Fixed inconsistent sorting in Archive Content list by correctly implementing descending order (sort then reverse) and adding a configuration loading guard to the liveQuery.
- Standardized safe data processing patterns in SVELTE_DEXIE_GUIDE.md.
- Performed minor cleanup and visibility logic hardening in Recovery Meetings module.
- Restored Event Session search by standardizing on 'event_id' for Dexie queries and implementing dual-layer filtering (local + API guard) to prevent broad results from clobbering filtered views.
- Advanced String-Only ID Standardization (Phase 2) by updating generic object processors across all event library modules to support both base IDs and legacy '_random' variants.
- Refactored Event Presenter and Presentation components to support standardized '_id_li' props while maintaining backward compatibility.
- Standardized common helper identifiers to snake_case (e.g., 'prevent_default') in the Events module.
- Verified Staff and Poster email notification logic in the Bulletin Board module.
- Updated .gitignore and cleaned up test artifacts.
- Updated 'method' parameter types in IDAA edit components to match the expected union types in the V3 API helpers.
- Applied type casting to 'liveQuery' results in the Archives page to ensure 'topic_name' and other convenience fields are recognized by the compiler.
- Fixed casting for 'method' variable in delete operations across Archives, Bulletin Board, and Recovery Meetings.
- Harden 'ae_events__event_presenter.ts' to ensure both ID and ID_random fields are synced during refreshes.
- Update presenter list wrapper to use '_id_random' for reliable Dexie queries.
- Enable 'inc_all_file_li' and 'inc_file_li' in session loaders to pre-cache nested files.
- Introduce 'Launcher_presentation_view.svelte' to explicitly display files linked to presentations.
- Refactor presenter views to focus exclusively on speaker-specific files, avoiding UI duplication.
- Roll out platform-wide standard for unauthenticated binary access using '?key=[account_id]' query parameter.
- Update API helpers (get, post, patch) to recognize 'key' bypass and strip account context headers accordingly.
- Refactor IDAA Bulletin Board to restore inline image rendering and edit-mode previews.
- Modernize Events Launcher (Layout, Sync, Session View) to use V3 Action URLs with verified auth.
- Update HTML generators in 'ae_utils.ts' to support the new authenticated URL structure.
- Harden 'ae_comp__event_file_obj_tbl' CSV export and clipboard links with V3 standard patterns.
- Add 'show_direct_download' prop to support native browser downloads via V3 Action paths (/v3/action/...).
- Integrate 'shorten_filename' logic directly into the component via 'max_filename' prop.
- Refactor internal UI to snippets for consistent rendering across button and anchor modes.
- Update 'ae_HostedFile' type with new SQL view fields: 'filename_no_ext' and 'filename_w_ext'.
- Add direct download toggle and edit mode indicator to testing dashboard.
- Simplify 'ae_comp__event_file_obj_tbl' by removing manual snippets in favor of standardized component logic.
- Created AE_Comp_Hosted_Files_Download_Button using Svelte 5 and Lucide icons.
- Added file_extension_icon_lucide utility for direct Lucide icon mapping.
- Refactored download logic to core__hosted_files.ts using V3 Action endpoint (/v3/action/hosted_file/.../download).
- Integrated new component into Event File Object Table.
- Cleaned up legacy window.postMessage calls in several file management views.
NOTE: The new download component is currently in development and may not be fully functional.
- Refactored 'event_session', 'event_presentation', 'event_file', 'event_location', and 'event_presenter' libraries to follow a Stale-While-Revalidate pattern.
- Implemented non-blocking background refreshes to allow instant UI rendering from IndexedDB cache.
- Parallelized nested collection loads (files, presentations) during background tasks to eliminate UI stalls.
- Standardized ID lookups using specific indices (event_location_id, event_id) for reliable local data retrieval.
- Resolved regression where aggressive ID overwriting caused relationship inconsistencies in background loads.
- Fixed reactive access to live queries in 'session_view.svelte' using proper Svelte 5 prefixing.
- Introduced 'Launcher_Cfg_Section' with 3-way state support (collapsed, auto, pinned).
- Implemented 'handle_section_expand' coordinator in 'launcher_cfg.svelte' for single-active-section behavior.
- Overhauled all configuration sub-components to participate in the auto-collapse logic.
- Updated 'ae_events_stores.ts' with new persistent section states.
- Synchronized 'launcher_cfg_template.svelte' with the new pattern for future extensions.
- Expose new native handlers in electron_relay.ts (Wallpaper, Updates, Window Control, Power).
- Overhaul Native OS Management UI with controls for window states, display layouts, and power.
- Add Application Updates component with support for local and web sources.
- Include confirmation modal for dangerous system actions (shutdown/reboot).
- Update TODO.md to mark Phase 5 integration as completed.
Update layout.ts to clean raw data from the native bridge. Initialize events_slct.event_device_id in launcher layout. Resort device_id prioritization in launcher_background_sync.svelte.
- Synchronize Functional Spec with new system handlers (Wallpaper, Power, Recording, Displays).
- Update Automation Scripts with finalized AppleScript handlers for PowerPoint and Keynote.
- Mark IDAA and Phase 5 automation tasks as completed in TODO.md.
- Refactor Jitsi reports to use SvelteKit streaming with a skeleton loader.
- Add conference lifecycle event listeners (left, close) to video conference page.
- Implement manual Novi data re-sync and improve initialization robustness.
- Fix skeleton visibility by using standard Tailwind colors.
- Fix infinite loops in '+layout.svelte' by using 'untrack' for store synchronization effects.
- Correctly define 'ae_acct' as a derived rune to resolve ReferenceErrors and ensure site styles hydrate properly.
- Modernize 'ae_comp__badge_template_form.svelte' with Svelte 5 Runes and callback props (onsuccess, onerror, oncancel).
- Fix illegal async in badge form by moving logic to a dedicated load function untracked by the effect.
- Add Presentation Management Reports to TODO list for tracking.
- Standardize 'ae_BaseObj' and event types in 'ae_types.ts' to handle nullable fields from V3 API/Dexie.
- Modernize Person, Address, and Contact forms with Svelte 5 Runes and reactive synchronization.
- Refactor Event Settings and its sub-components to use the 'onsave' callback pattern, removing deprecated dispatchers.
- Hardened 'element_data_store_v2' with safe initialization and localStorage caching logic.
- Clean up unused 'element_data_store.svelte' (V1) and suppress Electron environment type errors in 'tmp_shell_handlers.ts'.
- Update documentation and workspace settings to reflect Phase 5 reactive patterns.
- Implemented V3-style reactive search (Local Cache -> Remote Revalidation) for exhibitors.
- Standardized search fields to 'name' for Exhibits and 'event_badge_full_name' for Lead Tracking.
- Refactored Leads UI with standardized search components and grid layout.
- Updated event routing to exclusively use string-based IDs (Triple-ID pattern).
- Hardened 'ae_EventSession' type definitions to handle null values from V3 API/Dexie.
- Migrated Session search to the debounced pattern with Search Guards and shared observables.
- Implemented 'Remote First' toggle support (Edit Mode only) for background-only revalidation.
- Resolved 'each_key_duplicate' crash and fixed icon scaling issues in the session list.
- Restored missing session alert visibility for managers.
- Standardized store initialization to prevent reactivity loops.
- Finalized the rename of 'default_qry_string' to 'default_qry_str' across the Badge library, Dexie schema, and API documentation.
- Synchronized all reactive filtering in '+page.svelte' with the standardized field names to restore full search functionality.
- Verified that text search, affiliations, and types now correctly map to whitelisted backend fields.
- Renamed 'default_qry_string' to 'default_qry_str' across the Badge interface, Dexie schema, and API logic to resolve 400 'Unauthorized search field' errors.
- Synchronized local Fast Path filtering with the correct database field names.
- Hardened the reactive search pattern in '+page.svelte' to ensure end-to-sync consistency between local and background results.
- Implemented Search Guard and store initialization in '+page.svelte' to fix loops and filtering.
- Updated 'ae_comp__badge_search.svelte' to act as a pure UI component triggering versioned searches.
- Refactored 'ae_comp__badge_obj_li.svelte' to use shared data streams and fixed icon build errors.
- Corrected 'search__event_badge' to use the valid 'default_qry_str' field name, resolving 400 errors during text search.
- Re-implemented the standardized debounced search pattern in '+page.svelte' with a robust Search Guard to eliminate loops.
- Hardened Fast Path local filtering to match the 'Badge' schema and synchronized result sorting with API revalidation.
- Updated 'ae_comp__badge_search.svelte' and 'ae_comp__badge_obj_li.svelte' to align with the shared data pattern and fixed Lucide icon imports.
- Ensured 'events_loc' store initialization for new search-related fields.
- Implemented a 'Search Guard' pattern in '+page.svelte' that snapshots search criteria and bails out of redundant executions.
- Stabilized reactivity by removing immediate list clearing in Remote First mode, ensuring a consistent data stream.
- Isolated all search-driven state updates with 'untrack' to prevent circular dependency triggers.
- Hardened the 'lq__journal_entry_obj_li' observable to ensure stable result emission.
- Refactored 'src/routes/journals/[journal_id]/+page.svelte' to use a singleton 'liveQuery' observable, eliminating the search subscription loop.
- Synchronized entry counts in 'ae_comp__journal_obj_id_view.svelte' with the shared entries observable, ensuring accurate header feedback.
- Cleaned up 'ae_comp__journal_entry_obj_li.svelte' by removing redundant script blocks and hardening visibility filters.
- Restricted the result count badge to Edit Mode for a cleaner standard user interface.
- Improved initial load UX with localized spinners and proper undefined-state guards.
- Implemented the loop-proof search pattern in 'src/routes/journals/[journal_id]/+page.svelte' using versioned triggers and untracked dependency isolation.
- Stabilized data loading by introducing 'ae_comp__journal_entry_obj_li_wrapper.svelte', ensuring smooth SWR transitions between local and API results.
- Synchronized result counts with the rendered list in 'ae_comp__journal_entry_obj_li.svelte' and implemented permissive visibility defaults.
- Added 'Remote First' toggle support (Edit Mode only) and laid groundwork for Global Search in 'ae_comp__journal_entry_obj_qry.svelte'.
- Updated Dexie schema and API helpers to support future person-level cross-journal searching.
- Stabilized the 'liveQuery' observable in 'ae_idaa_comp__event_obj_li_wrapper.svelte' by re-wrapping it in '', ensuring smooth UI updates when switching between local and API results.
- Reduced search debounce time to 250ms in '+page.svelte' for a more instantaneous user experience.
- Hardened 'Fast Path' local filtering to be more permissive and added debug logging to help diagnose IndexedDB sync issues.
- Restricted the visibility of the 'Remote First' toggle to Edit Mode (.edit_mode) for a cleaner standard user interface.
- Replaced boolean search triggers with a versioned integer pattern to break reactivity loops in '+page.svelte'.
- Resolved 'behind by one' search lag by ensuring the UI immediately reflects local results, even if empty.
- Corrected subscription syntax for 'liveQuery' observables in 'ae_idaa_comp__event_obj_li.svelte'.
- Implemented a robust 'Remote First' toggle support in search logic while maintaining fast-path local feedback.
- Hardened Fast Path filtering to match server-side logic more accurately for improved cache reliability.
- Standardized the search pattern using Svelte 5 debounced $effects in Recovery Meetings, Badge Search, and Journals to eliminate manual triggers and stuttering.
- Fixed a bug in 'ae_idaa_comp__event_obj_li.svelte' where the Results count was inconsistent with the displayed list by implementing a derived 'visible_event_obj_li' state.
- Hardened 'ae_idaa_comp__event_obj_li_wrapper.svelte' to filter out 'undefined' entries from database 'bulkGet' calls.
- Cleaned up legacy search handling code in 'ae_idaa_comp__event_obj_qry.svelte'.
- Updated 'TODO.md' and 'GEMINI.md' to reflect search logic hardening accomplishments.
- Documented the 'Non-Blocking Load Pattern' (SWR) in 'documentation/PERFORMANCE_GUIDELINES.md' to prevent future performance regressions.
- Refactored 'src/routes/core/people/[person_id]/+page.ts' to be non-blocking, improving perceived speed for person details.
- Updated 'GEMINI.md' standards and 'TODO.md' tasks to reflect system-wide performance hardening.
- Eliminated blocking 'await' calls in '+page.ts' for main Events, Session, Location, and Presenter views.
- Implemented Stale-While-Revalidate (SWR) pattern: pages now render instantly using cached IndexedDB data while API refreshes run in the background.
- Removed expensive sequential loops in load functions, allowing the reactive UI (via LiveQuery) to handle data arrival as background tasks complete.
- Standardized Event module logic files to strictly use random string IDs, ensuring frontend consistency and avoiding 'Integer Trap' 404s.
- Refactored 'ae_comp__event_session_alert.svelte' to accept a plain object instead of a store/observable, resolving the 'store_invalid_shape' error in list loops.
- Updated all callers of the alert component to pass unwrapped session objects.
- Cleaned up event-related UI components to remove redundant '_random' field lookups and align with V3 CRUD patterns.
- Updated project metadata (GEMINI.md, TODO.md) to reflect the new memory structure and latest hardened state.
- Implemented 'Details' toggle in Session lists to defer expensive presenter/file sub-component renders, improving initial hydration speed.
- Fixed line-wrapping for long lines in Journals Editor (both plain textarea and CodeMirror).
- Updated TODO.md and GEMINI.md to reflect Journals standardization and performance optimizations.
- Renamed all Journal components to follow the ae_comp__* snake_case convention.
- Normalized all custom event handler props from PascalCase (onSave) to snake_case (on_save) across the module.
- Migrated all icon imports from @lucide/svelte to lucide-svelte for consistency.
- Resolved ReferenceErrors and file corruption issues in Journals config and entry views.
- Updated qry__journal_entry logic to support category filtering.
- Verified module integrity and component interop.
- Fixed invalid JavaScript-style comments inside HTML tags.
- Restored intentional '1 == 3' business logic pattern.
- Switched to 'event.currentTarget' for FormData instantiation.
- Resolved type errors in delete method calls via explicit casting.
- Cleaned up unused props and resolved property access issues in Archives and Bulletin Board components.
- Verified final state with npm run check.
- Updated ae_types.ts with missing IDAA-specific fields for Archives and Events (topic_name, archive_on, contact_li_json, etc.) using snake_case.
- Refactored bulletin board post filter to safely handle null archive_on dates.
- Fixed missing 'data' prop assignment in bulletin board list component to resolve type error.
- Corrected core_func.download_export__obj_type method name in recovery meetings export.
- Hardened safety checks for contact_li_json in recovery meetings view logic to prevent null property access.
- Mapped Jitsi meeting event data to internal snake_case variables and fixed input type assignments.
- Updated project documentation (TODO, GEMINI.md, .ae_brief) to reflect IDAA hardening progress.
- Re-enabled automatic service worker registration in svelte.config.js.
- Implemented a production-ready service-worker.js using SvelteKit metadata (-worker).
- Added intelligent caching for build artifacts, static files, and network fallbacks.
- This ensures the UI shell and assets load instantly from disk, completing the hydration performance workstream.
- Added visual progress bars for RAM usage based on native device metadata.
- Implemented color-coded status indicators (success/warning/error) for memory usage.
- Enhanced layout with animated pulses for heartbeat and sync status.
- Added detailed device info display including hostname and IP addresses.
- Refactored Events root layout to fire load_ae_obj_id__event in the background.
- Refactored Journals list page to fire load_ae_obj_li__journal in the background.
- Combined with SWR module logic, this ensures near-instant UI rendering from Dexie cache while refreshes occur asynchronously.
- Implemented SWR pattern for Journal and Site Domain lookups.
- Refactored +layout.ts across modules to fire background refreshes instead of blocking.
- Updated +layout.svelte to render the layout shell immediately while hydrating.
- Silenced 'AbortError' and 'NetworkError' in api_get_object.ts and api_post_object.ts for log_lvl 0.
- Resolved duplicate export errors in ae_journals__journal.ts.
- Implemented SWR pattern for session loading in ae_events__event_session.ts.
- Refactored Launcher layouts (+layout.ts and +page.ts) to fire background refreshes instead of awaiting API calls.
- Removed redundant blocking logic to ensure instant UI rendering from Dexie cache.
- Refactored lookup_site_domain_v3 to be cache-first, unblocking root layout.
- Implemented Stale-While-Revalidate (SWR) pattern for load_ae_obj_id__event.
- Added global hydration overlay and loading spinner to +layout.svelte.
- Updated site domain whitelist to persist critical account/styling metadata in Dexie.
- Refactored root load function to return immediately if cached site data is found.
- Broke down the massive launcher_cfg.svelte into 7 modular sub-components.
- Updated electron_relay.ts with Phase 5 presentation controls and manifest tools.
- Updated architecture documentation to reflect the new TypeScript-based native bridge.
- Upgraded LauncherBackgroundSync to force-hydrate OS metadata (home/tmp) on mount.
- Hardened electron_relay.ts with robust placeholder resolution and global regex.
- Restored safe handover by making native.launch_from_cache presentation-aware.
- Integrated heartbeat and sync status into the formal Launcher Config UI.
- Added comprehensive technical documentation for the 2026 native architecture.
- Reverted handle_open_file to use native.launch_from_cache for atomic copy support.
- Upgraded Electron main process to handle specialized launcher logic (LibreOffice/AppleScript) internally after the copy operation.
- Ensures absolute paths and file existence before triggering OS apps.
- Removed detailed OS path hydration from root +layout.ts to keep it lean.
- Moved home/tmp directory hydration to LauncherBackgroundSync onMount.
- Fixed regex pattern bug in electron_relay.ts (/\[home\]/g).
- Ensures absolute paths are correctly generated within the Launcher module.
- Officially marked Phase 4 (Heartbeat/Sync) as complete.
- Updated GEMINI.md development history and session context.
- Set Phase 5 (AppleScript/Office) as the next active workstream.
- Added logic to automatically JSON.stringify any field ending in '_json' in V3 API helpers.
- Added final payload logging to create_ae_obj_v3 for better debugging.
- Resolves 'str type expected' validation errors (HTTP 400) when sending objects to V3 CRUD endpoints.
- Removed manual 'Z' suffix addition in device processing to fix incorrect offsets.
- Switched heartbeat payload to standard UTC ISO strings.
- Enabled background timers in non-native environments for easier verification.
- Hardened telemetry gathering to skip Electron-only APIs when running in a browser.
- Explicitly added the 'x-account-id' header to resolve HTTP 403 error when fetching Jitsi activity logs.
- Ensures the server-side scope check passes for these report requests.
- Added Array.isArray check to prevent 'not iterable' error on API failure responses.
- Restored params_json to narrow the activity_log query and prevent potential timeouts.
- Cleaned up minor whitespace in sort logic.
- Hardened 'find_object_id' in Dexie to support 'event_' prefixed IDs.
- Implemented singleton 'LauncherBackgroundSync' in root launcher layout.
- Added V3-compliant heartbeat and room refresh cycles.
- Fixed redundant component imports and Skeleton UI prop errors.
- Added 'inc_file_li' support to event API loader.
- Improved manual OS command input with better binding and extraction logic.
- Resolved '[object Object]' issue by correctly displaying stdout/stderr/error from structured return.
- Polished terminal output UI with better styling and clear functionality.
- Renamed section to 'Native OS Handlers & Folders' for better clarity.
- Updated electron_relay.ts with full Phase 3 command set (run_cmd_sync, kill_processes, get_device_info).
- Fixed 500 error in Event Location by standardizing enabled/hidden flags.
- Hardened native environment detection in root layout.
- Added Manual OS Command input to Launcher Configuration for Phase 3 testing.
- Updated load functions to pass inc_session_li and inc_all_file_li.
- Enhanced _handle_nested_loads to recursively fetch sessions and files.
- Ensured consistency with Triple-ID pattern and V3 API standards.
- Implemented aggressive room-wide background caching engine in LauncherBackgroundSync.svelte.
- Added inc_file_li and inc_all_file_li support to Event and Event Location object loaders for total room coverage.
- Switched to proven V1 download path (/hosted_file/) to resolve V3 CRUD binary delivery issues.
- Optimized Electron bridge with download locking and flat-hash storage matching legacy 'perfect' logic.
- Standardized all Electron IPC methods and parameters to snake_case.
- Added visual sync progress indicator for room caching status.
- Implemented Default, Onsite, and Native launcher modes in launcher_file_cont.svelte.
- Restored background pre-caching logic with configurable timers in new LauncherBackgroundSync component.
- Fixed standard browser download regression for regular website mode.
- Modernized electron_relay to TypeScript and standardized bridge detection in layout.
- Detailed startup and background sync technical flow in documentation.
- Added Query Limit configuration for Journals and Entries in modal settings.
- Refactored +page.svelte to delegate empty-state handling to the entry list component.
- Improved "no results" messaging in ae_comp__journal_entry_obj_li.svelte.
- Consolidated LiveQuery logic into [journal_id]/+page.svelte and removed redundancy from layout.
- Added automatic load trigger on journal_id change to ensure data freshness.
- Hardened Enabled/Hidden filters to correctly include NULL/undefined values in default views.
- Refined search behavior to distinguish between "default view" (null) and "no results found" ([]).
- Updated modal_journals_config.svelte with standardized module-level settings.
- Robust ID handling in bulk retrieval to handle varying property names (id, random_id).
- Simplified limit logic in search__event and qry_ae_obj_li__event_v2 to use the passed value directly.
- Added .slice(0, limit) to filtered results in Event module to handle over-fetching correctly.
- Explicitly passed the qry__limit from idaa_loc to the meeting list components.
- Applied .limit() to the Dexie liveQuery in the meeting list wrapper.
- Updated the UI result count and loop to strictly adhere to the requested limit.
- Restored legacy query aliases (qry_ae_obj_li__event, qry__event_file, etc.) across Event modules to fix build errors.
- Fixed Hosted File deletion logic in core__hosted_files.ts to use specialized /hosted_file/{id} endpoint with fake_delete support.
- Standardized ID field usage (hosted_file_id vs hosted_file_id_random) in Archive Content and Event File upload components.
- Updated TODO.md to reflect completed search restoration tasks.
- Backend: Updated `qry__journal_entry` in `ae_journals__journal_entry.ts`.
- Logic: Switched `default_qry_str` operator to `like` (from `match`) for V3 API compatibility.
- Fix: Corrected filter field names from `enabled`/`hidden` to `enable`/`hide`.
- Resilience: Added robust unwrapping of V3 API response envelopes (handling `{ data: [] }` vs array) to prevent iteration errors.
- Backend: Updated `search__event` in `ae_events__event.ts` to populate `params['lk_qry']`.
- Logic: Used `like` operator with wildcards for `default_qry_str` in `search_query` to match `event_session` pattern.
- Context: This fixes the text search in IDAA Recovery Meetings by ensuring the backend receives the expected V3 search parameters.
- Backend: Added V3-compliant `search__event` to `ae_events__event.ts` with support for `default_qry_str`.
- Backend: Removed accidental duplicate `qry_ae_obj_li__event_v2` implementation.
- API: Exported `search__event` from `ae_events_functions.ts`.
- Frontend: Updated `recovery_meetings/+page.svelte` to use the new `search__event` function.
- Mitigation: Disabled `serviceWorker.register` in `svelte.config.js` to stop the loop of `TypeError` during script evaluation.
- Debug Tool: Preserved the `fix-sw` cache clearing utility by moving it to `src/routes/testing/fix-sw/+page.svelte` for future investigation.
- Service Worker: Simplified `src/service-worker.js` to a minimal "Hello World" state and removed the problematic `.ts` version.
- Cleanup: Minor formatting adjustment in `ae_events_functions.ts`.
- Docs: Updated `TODO.md` and `GEMINI.md` to reflect the mitigation and planned follow-up.
- API: Updated `search_ae_obj_v3` to correctly serialize complex URL parameters (JSON).
- Events: Restored "sacred" business logic for Event Badge and Session searches using `ft_qry` and `lk_qry`.
- PWA: Fixed manifest path in `app.html` to resolve 404 errors.
- Documentation: Updated `GEMINI.md` and `TODO.md` with recent search hardening accomplishments.
- API Migration: Refactored core__hosted_files.ts and ae_events__event_file.ts to use AE API CRUD V3.
- Logic Hardening: Unified property processing via '_process_generic_props' and ensured 'id_random' string identifier consistency.
- Reliability: Hardened orphan cleanup logic in delete_ae_obj_id__hosted_file and aligned with V3 generic delete patterns.
- API: Correctly exported 'get_ae_obj_li_for_lu' and reverted to stable V2 endpoints (/v2/crud/lu/.../list) to resolve V3 500 errors.
- Auth: Injected 'x-no-account-id' bypass header for unauthenticated global lookup access.
- UI: Updated lookups page to support 'english_short_name' fallback for countries and added Country Subdivisions card.
- Debug: Added transient console logging for verification.
- Search Hardening: Implemented 'Inclusive OR' logic for physical/virtual filters and restored robust full-text search using 'default_qry_str' with wildcards.
- Crash Fix: Added null checks for 'contact_li_json' in meeting detail view to prevent TypeError.
- ID Stability: Enhanced 'core__idb_dexie.ts' to support 'id_random' mapping and fixed misleading save logging.
- Reliability: Both V2 and V3 search paths now consistently return processed objects with standardized IDs.
- IDAA Isolation: Created using legacy V2 endpoints and for Recovery Meetings stability.
- V3 Refinement: Implemented 'Body + Header' injection in to fix 'Integer Trap' while maintaining Auth scope.
- API Upgrade: Enhanced to support custom headers.
- Docs: Updated migration guide and development history with final isolation strategy.
Restored the use of 'for_obj_type' and 'for_obj_id' URL parameters for the 'event/search' endpoint. The backend permission middleware requires these URL parameters to validate the API Key scope for the Event object, whereas body-only injection (used in Archives/Posts) was causing a 403 Forbidden error for Events.
- Implemented Structured Error Handling across GET/POST/PATCH helpers to extract rich V3 error metadata.
- Added direct localStorage fallback for JWT detection to resolve race conditions during initial page load.
- Fixed async race condition in Archives leading to 'archive_content_li is undefined' crash.
- Hardened generic object processor to handle non-array API responses gracefully.
- Resolved zero-result bug in Event Search by using raw 'account_id_random' to bypass backend mapping conflicts.
- Isolated bootstrap headers in +layout.ts and removed invalid response headers from request config.
- Enhanced /testing dashboard with live header inspection and V3 hardening audits.
- Updated api_get_object and api_post_object to extract rich metadata (meta.details) from 400/500 responses.
- Enables frontend to bubble up specific DB schema, validation, and constraint errors for better debugging.
- Added 'V3 Hardening' section to /testing dashboard with automated tests for Permissive Mode and Structured Error extraction.
- Hardened 'Bootstrap Paradox' bypass logic in GET/POST helpers to only strip account ID if an intentional bypass value is provided.
- Enabled 'Permissive Update Mode' (x-ae-ignore-extra-fields: true) by default to improve frontend state synchronization.
- Fixed loader hydration bug where isolated API headers were being overwritten by stale global defaults.
- Ensured correctly resolved account names persist in local state instead of defaulting to 'Ghost Account'.
- Added Environment & Bridge diagnostics section to the testing dashboard for easier runtime verification.
- Resolved 'Ghost Account' warning by updating layout hydration to align with V3 ID Vision (account_id vs account_id_random).
- Improved site lookup reliability using Agent API Key and structured EQ filters for exact FQDN matching (including ports).
- Modernized PWA manifest with maskable icons (PNG/WebP), app shortcuts, and unique installation IDs.
- Implemented automatic Electron 'Native' mode detection in root layout.
- Fixed stale API URLs in Launcher native file download logic.
- Added V3 migration documentation and JWT verification test scripts.
- Incorporated comprehensive icon list from dgr.oneskyit.com reference.
- Updated dynamic naming logic to 'One Sky IT - {Account} Aether PWA'.
- Set display mode to 'fullscreen' per reference standard.
- Added verification tool to the Testing Dashboard.
- Added /manifest.webmanifest server-side route.
- Implemented hostname-based branding lookup using Agent API Key.
- Updated app.html to use the dynamic manifest route.
- Added manifest verification tool to the System Testing dashboard.
- Standardized ID usage: Migrated multiple components from '_random' variants to standard 'id' properties to align with V3 backend and Dexie patterns.
- Electron Integration: Added global 'native_app' declaration and Window interface augmentation in app.d.ts to resolve TypeScript errors.
- Type Safety: Enhanced ae_EventSession interface with missing 'event_file_li' and added runtime null checks in session loading logic.
- UI/UX: Restored missing launcher components in location-specific pages and fixed LiveQuery filter logic for session lists.
- Bug Fixes: Resolved indexing errors in location list and corrected store update patterns in layout.
- API: Standardized 'order_by_li' types in event and archive modules.
- API: Corrected 'enabled'/'hidden' parameter types in event exhibit and device search/list functions.
- Type Safety: Addressed generic type casting issues in _process_generic_props across event modules.
- Data Handling: Resolved return type consistency in journal creation and DB save operations.
- Parameter Management: Fixed missing 'try_cache' parameters in event exhibit functions.
- Core Logic: Ensured correct object properties in DB put operations for hosted files.
- Core: Use FormData in video clip components to fix EventTarget errors.
- DB: Rename 'File' to 'ae_LocalFile' in db_core to prevent DOM type shadowing.
- API: Strictly type 'order_by_li' across archives, events, and journals to match API definition.
- API: Fix 'enabled'/'hidden' parameter types in search functions.
- Generics: Add 'any' cast to 'processed_obj' in generic processors to fix indexing errors.
- Journals: Update journal_entry creation to return null on failure, fix missing ID fields in DB save.
- Update ae_types.ts with joined fields for deep layout loading
- Fix OrderBy vs OrderBy[] type mismatch in API v2/v3 and generic CRUD
- Apply 'as const' to order_by_li defaults in core/event libraries
- Resolve type errors in reports_presenters and reports_files Svelte components
- Resolved Svelte 5 $bindable() prop errors in Analytics and Person Table components.
- Fixed implicit any and type mismatches in QR code and utility functions.
- Corrected OrderBy type mismatch pattern in systemic loaders.
- Improved type safety for BlobPart and ArrayBuffer handling.
- Down to 700 errors from ~731.
- Added missing Addresses and Contacts links to Core Management main page.
- Modernized list pages for Accounts, Sites, Activity Logs, and Lookups.
- Standardized headers, iconography, and search layouts across all core list views.
- Improved layout responsiveness and visual hierarchy.
- Rebuilt Users and People search from scratch using robust flexbox (grow, min-w-100px, whitespace-nowrap).
- Added search functionality and card-grid layout to Address and Contact management lists.
- Modernized detail pages for Addresses and Contacts with standardized headers and iconography.
- Unified form padding (p-3 for inputs, p-2 for selects) across all Core modules.
- Fixed variable hoisting and missing icon imports in Address components.
- Standardized User Management search to match People Management style.
- Added 'p-3' padding to main search inputs and 'p-2' to selects for better visual clarity.
- Improved search icon spacing with enhanced shim padding (!px-4).
- Applied consistent padding enhancements to Account, Site, and User detail pages.
- Standardized 'Go' button prominence across search sections.
- Updated Person, Address, Contact forms with consistent Skeleton UI styling.
- Standardized Account, Site, and User detail pages.
- Modernized People, Address, and Contact list pages with improved headers and action buttons.
- Applied 'variant-filled-surface' and 'rounded-lg' patterns from Journals module for UI consistency.
- Updated load_ae_obj_li__user to handle 'All' scope by fetching account and global users separately.
- Ensured Search API uses authorized account_id_random field with op: 'eq' for null.
- Fixed 404 for global lookups by avoiding [NULL] in standard List API.
- Switched from 'op: is' to 'op: eq' for NULL values, as 'is' was returning 400.
- Refined search query structure to ensure compatibility with user search endpoint.
- Refined load_ae_obj_li__user to use List API for simple account filtering and Search API for complex cases.
- Fixed 'Global Only' scope returning all users by ensuring correct NULL filtering.
- Improved search query structure with top-level 'or' for inclusive global support.
- Added detailed logging to load_ae_obj_li__user for easier debugging.
- Updated load_ae_obj_li__user to use search_ae_obj_v3 for complex account OR global filtering.
- Fixed zero-results issue by defaulting to 'All' scope (Current Account + Global).
- Added visual 'Account' vs 'Global' badges to user cards.
- Added 'Scope' selector to User Management page.
- Updated load_ae_obj_li__user to support for_obj_id filtering.
- Updated create_ae_obj__user to support account_id for new users.
- Updated User Management page to default to current account_id for listing and creation.
- Improved Person table with 'Not Linked' badge for better visibility.
- Updated Person form with 'prefix' field and direct User account linking.
- Standardized Address and Contact list views to show all records (enabled/hidden).
- Amending previous refactor to include all verified import and type fixes.
- Unified Person and User logic into ae_core__* counterparts and marked legacy files.
- Renamed Activity Log to ae_core__activity_log.ts for naming consistency.
- Updated all core function imports across the identity, logs, and video conference modules.
- Fixed missing 'prefix' field in Person form payload and corrected return types in Activity Log.
- Updated project TODO to reflect completed core module refinements.
- Refactored authentication calls in core__user.ts to explicitly set x-account-id and remove x-no-account-id, ensuring correct account context for legacy endpoints.
- Updated emailed sign-in link logic to use the correct /user/{user_id}/email_auth_key_url endpoint and avoid 500 crashes caused by extraneous URL parameters.
- Fixed person search query field names (enable/hide) and broadened search scope to 'all' to ensure records are found regardless of status.
- Added safety checks and documentation to prevent UI crashes when API responses are empty or NULL.
- Implemented 'untrack' safeguards in all modal effect blocks to prevent reactivity loops.
- Standardized Module, Journal, and Entry configuration with unified tabbed UI and Aether Orange theme.
- Fixed critical 'journal is undefined' and ReferenceErrors in configuration modals.
- Restored missing button toggles and visibility options in journal-level settings.
- Documented Dexie liveQuery '$' prefix mandate in GEMINI.md and project documentation.
- Eliminated legacy JournalEntry_SettingsMenu in favor of ModalJournalEntryConfig.
- Refactored Module, Journal, and Entry configs to use consistent tabbed layouts.
- Adopted unified 'Aether Orange' aesthetic with Skeleton UI and Lucide icons.
- Replaced overcrowded inline settings menu with 'ModalJournalEntryConfig'.
- Cleaned up Header component logic and improved visual consistency.
- Added 'Force Reset to Plain Text' button in the editor for decryption failure states.
- Guarded reset functionality with '.edit_mode'.
- Refactored background sync logic to properly isolate 'untrack' calls and prevent content resets.
- Refactored effects to use selective tracking and 'untrack' for all store reads.
- Implemented manual 'deep_copy' to handle Svelte 5 reactive proxies safely.
- Added concurrency locks ('is_processing') to decryption and save workflows.
- Stabilized state synchronization to prevent circular reactivity loops.
- Refactored all effects to use 'untrack' for rigorous dependency isolation.
- Implemented 'deep_copy' helper to safely handle Svelte 5 reactive proxies.
- Added concurrency locks to 'update_journal_entry' and decryption workflows.
- Completed integration of 'JournalEntry_Metadata' component.
- Refactored effects to use 'untrack' for store isolation.
- Implemented content normalization in 'has_unsaved_changes' to avoid circular triggers.
- Ensured atomic state sync after successful PATCH updates.
- Added comprehensive flow diagnostics to track effect execution.
- Cleared 'content_encrypted' from local state upon successful decryption to update UI.
- Modified background sync effect to preserve decrypted content during active sessions.
- Enhanced decryption logging for better diagnostics.
- Extracted journal entry decryption workflow to 'ae_journals_decryption.ts' to decouple it from Svelte reactivity and address loop issues.
- Updated 'ae_comp__journal_entry_obj_id_view.svelte' to use the new decryption helper.
- Refactored 'Quick Add' to use the first line as the title and remove it from the content before saving.
- Added 'ae_journals_export_templates.ts' with Markdown, HTML, and JSON support
- Refactored 'ae_comp__modal_journal_export.svelte' to use the new template system
- Optimized bulk export with automated template selection based on Journal type
- Integrated 'Import Entries' action directly into the Journal menu
- Updated project documentation to reflect portability features and implementation status
- Marked Phase 2 (Rapid Entry) and Phase 3 (Content Portability) as complete
- Logged new Auto-Save functionality in Phase 4
- Updated TODO.md and module documentation to reflect current state
- Added 'auto_save' preference to journals store
- Added toggle and status indicators (Check/X/Loader) to JournalEntry_Header
- Implemented auto-save logic with debounce in Journal Entry view
- Added 'ae_journals_parsers.ts' with Standard, Personal Log, and Amazon Vine parsers (ported from Python)
- Created 'AeCompModalJournalImport' for file selection, preview, and API submission
- Integrated Import button into Journals list view
- Created AeCompJournalEntryQuickAdd for high-velocity note creation
- Extracted robust append/prepend logic from List View into AeCompModalJournalEntryAppend
- Unified List and Detail views to use the shared modal for content manipulation
- Added explicit Append/Prepend actions to Journal Entry settings menu
- Updated TODO.md and Journals module documentation
Binding to an undefined value when a $bindable prop has a default fallback causes a runtime error in Svelte 5 runes mode. Defaults are now applied within the component logic.
- Implement new Svelte 5 Person, Address, and Contact form components with surgical payload logic.
- Refactor core routes (People, Addresses, Contacts) to support unified Create/Edit workflows.
- Update ae_types.ts, db_core.ts, and db_journals.ts to align with V3 backend object models.
- Fix type safety issues in Journal history views and refine metadata display.
- Migrate person core functions to the newer ae_core__person module.
- Synchronized Lucide icon imports with template usage.
- Removed all redundant local state and legacy crypto comments.
- Verified stable integration of modular AITools, ObjectFlags, and MetadataFooter.
- Standardized Svelte 5 logic for better maintainability and token efficiency.
- Extracted complex View/Edit logic into standalone JournalEntry_Editor.svelte.
- Integrated modular editor with bindable state and editorView.
- Maintained support for CodeMirror, Plain Text, and Rendered HTML modes.
- Simplified main view by removing another 300+ lines of template logic.
- Added a dedicated gear button to open the AI modal directly to the Settings tab.
- Resolved the 'chicken and egg' problem where settings were inaccessible if the AI process failed.
- Improved layout consistency with inline-flex wrapper.
- Implemented 'Demo Mode' fallback when no API token is provided.
- Ensured the AI modal opens even on connection errors to display troubleshooting info.
- Improved user feedback with simulated loading states for demo data.
- Enhanced AE_AITools with a Settings tab for model, prompt, and parameter configuration.
- Connected AE_AITools to Journals state via two-way bindings for persistent configuration.
- Removed redundant AI settings from Journals config modal.
- Standardized Svelte 5 patterns for cross-module component configuration.
- Created AE_ObjectFlags.svelte for standardized flag management across all modules.
- Created AE_MetadataFooter.svelte for unified creation/update/original timestamp display.
- Modularized Journal Entry view by replacing manual flag and footer logic with generic elements.
- Improved code reusability and reduced main component complexity.
- Created reusable AE_AITools.svelte in src/lib/ae_elements for system-wide AI features.
- Refactored Journal Entry view to utilize the generic AI toolset.
- Cleaned up redundant module-specific AI logic and modal code.
- Standardized Svelte 5 patterns for AI summary results.
- Extracted AI summarization logic and Modal into standalone JournalEntry_AITools component.
- Applied Prettier formatting to main component for consistent structure.
- Removed further blocks of legacy script and template code.
- Re-verified stability of core features (decryption, saving, metadata).
- Restored accidentally removed article tag and corrected syntax errors.
- Cleaned up extensive blocks of redundant comments and legacy code in the script and template.
- Refined component structure to serve as a lean template for other Aether modules.
- Re-verified all core UI logic (edit mode, decryption, data sync).
- Cleaned up redundant imports, legacy script stubs, and dead code.
- Removed extensive legacy comments from history and AI summary template sections.
- Verified stability of core decryption and data sync logic.
- Standardized file structure to serve as a better template for other Aether modules.
- Extracted AI Tools and Metadata into dedicated Svelte 5 components (JournalEntry_AITools, JournalEntry_Metadata).
- Standardized iconography to Lucide across list and detail views.
- Refined sort order controls with improved styling and Lucide icons.
- Fixed 'OpenAI is not defined' ReferenceError by restoring necessary imports.
- Corrected component naming discrepancy for Journal_entry_obj_file_li.
- Hardened change detection using Svelte 5 .by for reliable Save button behavior.
- Restored sync_config__event_pres_mgmt in ae_events__event.ts to resolve import errors.
- Removed unused OpenAI import in journal entry view.
- Removed unused Html5QrcodeScannerState import in QR scanner component.
- Improved build consistency for staging and production environments.
- Implemented 3-state (View/Eye/Save) toggle in journal entry header with Lucide icons.
- Hardened change detection logic using Svelte 5 .by for reliable UI updates.
- Improved header responsiveness and scaling across device sizes.
- Commented out experimental CodeMirror toolbar to maintain stability while keeping the helper code for reference.
- Completed the unified type system in ae_types.ts covering all 42 active Aether objects.
- Scaffolded missing core logic modules for Organization, EventTrack, and Sponsorship with standardized return types.
- Updated project roadmap in TODO.md to reflect mission completion on foundational type parity.
- Replaced dangerous custom dispatch override with EditorView.updateListener.
- Resolved 'Uncaught TypeError: child is undefined' by allowing CodeMirror to manage its own update cycle.
- Improved Svelte state syncing for new_content.
- Added formatting toolbar to journal entry editor with support for bold, italic, headers, and lists.
- Standardized iconography to Lucide across Journals module, removing legacy FontAwesome.
- Improved responsiveness and dark mode compatibility in layout and list views.
- Refactored CodeMirror component to support external control via editorView binding.
- Hardened security by removing unnecessary @html tags in journal names.
- Added detailed fields for ae_EventPerson, ae_EventRegistration, ae_AccountCfg, and others to ae_types.ts.
- Replaced local interfaces in ae_journals__journal.ts with unified imports.
- Standardized Promise return types for all core data loading, creation, search, and update functions in Journal module.
- Finalized migration for primary Identity and Journal objects.
- Added ae_EventExhibit and ae_EventExhibitTracking interfaces to ae_types.ts.
- Replaced local interfaces in ae_events__exhibit.ts with unified imports.
- Standardized Promise return types for all core data loading, creation, and update functions in Exhibit and Tracking logic.
- Integrated triple-ID patterns for lead retrieval and booth management components.
- Integrated ae_Post, ae_PostComment, ae_Page, and ae_Archive types into logic files.
- Replaced local interfaces in ae_posts__post.ts and ae_posts__post_comment.ts.
- Standardized Promise return types for all CRUD and loading functions in Post and Presentation modules.
- Synchronized event presentation logic with V3 triple-ID patterns.
- Added ae_EventFile, ae_EventDevice, ae_EventAbstract, and ae_Organization to ae_types.ts.
- Replaced local interfaces in EventFile and EventDevice logic files with unified imports.
- Standardized Promise return types for all core data loading, creation, search, and update functions.
- Integrated triple-ID patterns for Dexie and V3 API parity across files and devices.
- Added ae_HostedFile, ae_HostedFileLink, ae_DataStore, ae_Post, ae_PostComment, ae_Page, ae_Archive, and ae_ArchiveContent to ae_types.ts.
- Replaced local interfaces in core__hosted_files.ts and core__data_store.ts with unified imports.
- Replaced local interfaces in ae_archives/*.ts with unified imports.
- Standardized Promise return types for all core data loading, creation, search, and update functions across migrated modules.
- Synchronized storage and archival modules with Backend V3 field naming and Triple-ID patterns.
- Added ae_EventLocation, ae_EventSession, ae_EventPresenter, and ae_EventTrack to ae_types.ts.
- Replaced local interfaces in logistics logic files with unified imports.
- Standardized Promise return types for all core data loading, creation, search, and update functions.
- Integrated triple-ID patterns for Dexie and V3 API parity across locations, sessions, and presenters.
- Added ae_Address and ae_Contact interfaces to ae_types.ts.
- Replaced local interfaces in Address and Contact logic files with unified imports.
- Standardized Promise return types for all core data loading, creation, and update functions in migrated modules.
- Refined field optionality to align with frontend CRUD requirements.
- Added ae_User and ae_ActivityLog interfaces to ae_types.ts.
- Replaced local interfaces in User and ActivityLog logic files with unified imports.
- Standardized Promise return types for all core data loading, creation, and update functions.
- Verified permission field alignment for User type to support hierarchical access logic.
- Updated ae_types.ts with ae_Event, ae_EventBadge, and ae_EventBadgeTemplate interfaces.
- Standardized return types (Promise<ae_Event[]>, etc.) for all core loading and CRUD functions in Events module.
- Integrated triple-ID patterns for Dexie and V3 API parity in event-related objects.
- Cleaned up local interface redundancies in favor of unified imports.
- Updated ae_types.ts with detailed fields for ae_Person, ae_Site, and ae_SiteDomain based on V3 backend exports.
- Replaced local interfaces in ae_core__person.ts and ae_core__site.ts with unified imports.
- Added explicit Promise return types to all core data loading and CRUD functions in Person and Site modules.
- Standardized triple-ID pattern and return signatures across core identity and configuration modules.
- Created src/lib/types/ae_types.ts to serve as the central repository for Aether object interfaces.
- Standardized interfaces to include the triple-ID pattern (id, [type]_id, [type]_id_random) for Dexie and V3 API compatibility.
- Migrated Account and JournalEntry modules to use ae_Account and ae_JournalEntry types.
- Added explicit Promise return types to all core data loading, creation, and update functions in migrated modules.
- Unified and hardened get, post, patch, and delete helpers with standardized retry logic, kebab-case headers, and V3 response envelope handling.
- Implemented robust 'Bootstrap Paradox' resolution logic across the API stack to handle unauthenticated site domain lookups safely.
- Enhanced API helpers to support custom fetch injection, enabling reliable server-side execution in SvelteKit.
- Upgraded /testing page into a comprehensive System Testing Dashboard for core helper and V3 search verification.
- Updated TODO.md and GEMINI.md with 2026-01-08 session learnings and 'Frontier Journals' vision.
- Changed default 'enabled' and 'hidden' filters to 'all' for activity logs
- Refactored qry__activity_log to avoid sending empty 'and' arrays
- Ensured consistent filtering between API function and UI
- Updated qry__activity_log to support filtering by person_id
- Created /core/activity_logs standalone page for monitoring system actions
- Enhanced Person detail page with 'Recent Activity' column showing real data
- Added 'Activity Logs' card to the Core Management dashboard
- Created .editable_fields.ts files for Journals, Events, and Sponsorships modules
- Standardized the whitelist pattern across the entire codebase
- Updated TODO.md and GEMINI.md with recent accomplishments and learnings
- Ensured 'account_id' is injected into post objects during processing to maintain IndexedDB filter consistency
- Resolved race condition by awaiting database clearing before refreshing posts
- Corrected 'archive_on' date comparison logic in BB component
- Exported 'qry__post' and enabled comment fetching during post search
- Updated GEMINI.md and TODO.md with project progress
- Resolved Svelte 5 / SvelteKit SSR errors by adding browser checks for window.postMessage and Dexie database operations
- Prevented side effects on global state during detail page preloading by refactoring people/[person_id]/+page.ts to use shallow copies
- Implemented full V3 CRUD support, detail pages, and editable_fields for Address and Contact modules
- Enhanced Event and Post search to support filtering by person_id, enabling real related data in the Person detail view
- Fixed missing onMount import in Person detail component
- Restored 'conference' search filter in ae_events__event.ts (now supported by backend)
- Maintained local filtering as a safety fallback
- Finalized Bulletin Board (Posts) V3 migration
- Added editable_fields for all remaining IDAA-related core objects
- Migrated Post and Post Comment modules to Aether API CRUD V3
- Added editable_fields for Posts and Post Comments
- Implemented local filtering for 'archive_on' in BB route as V3 workaround
- Verified IDAA Archives and Recovery Meetings are functional on V3
- Ensured all IDAA logic follows the API -> Processor -> DB Save pattern
- Implemented workaround for 'conference' field search restriction in V3 by using local filtering
- Optimized post_object to stop retrying on 4xx client errors (400, 401, 403)
- Migrated Archives and Event/Recovery Meeting modules to Aether API CRUD V3
- Added editable_fields definitions for Archive and Archive Content objects
- Implemented lookup_site_domain_v3 in ae_core__site.ts using Search API
- Refactored root +layout.ts to use the new V3 lookup logic
- Standardized top-level data initialization with V3 response metadata
- Improved security by transitioning to standardized V3 API patterns
- Fixed broken import path in /core dashboard
- Simplified core layout data requirements to prevent crashes
- Implemented V3 CRUD and Dexie tables for Addresses and Contacts
- Created placeholder management pages for /core/addresses and /core/contacts
- Added 'Linked Activity & Content' section to Person detail page to show related events and posts
- Added dynamic route for user details with manager-level access
- Implemented permission management UI (Super, Manager, Admin, Verified)
- Added account status toggles and timestamp visibility
- Integrated with V3 User CRUD logic and editable field whitelist
- Renamed person route to people for consistency
- Refactored /core dashboard to use standard <a> links
- Added common management <nav> to core layout
- Replaced goto() with standard links across all list and detail pages
- Fixed 500 error caused by broken import path and strict layout data
- Created Comp_person_search component for dashboard integration
- Improved reactivity in person results table using derived liveQuery
- Implemented User Account Linking UI in Person detail page
- Added logic to filter and link unlinked users to person records
- Modernized Core Dashboard with person search integration
- Completed V3 migration for Event Badge CRUD operations
- Implemented User module V3 logic and editable fields
- Created management routes for Accounts, Sites, Users, and Lookups
- Updated Site Domain logic to use 'fqdn' and show 'access_key'
- Modernized Core Dashboard with navigation cards
- Restored Dexie User table definition
- Created V3 logic files: ae_core__account.ts, ae_core__site.ts, ae_core__person.ts
- Standardized on API -> Processor -> DB Save pattern
- Added editable_fields definitions for Account, Site, Person, and Activity Log
- Updated Activity Log module to use V3 wrappers
- Updated Dexie schema with account, site, and site_domain tables
- Added V3 tests to testing page
- Added 'Test Journal Module Soft Deletes' to the testing page.
- Verified 'disable' and 'hide' methods for journal entries.
- Ensured all Journals CRUD operations utilize the standardized V3 wrappers.
- Documented successful end-to-end testing of the new API patterns.
- Added update_ae_obj_v3, update_nested_obj_v3, delete_ae_obj_v3, and delete_nested_ae_obj_v3.
- Refactored Journals and Journal Entries modules to utilize the new V3 API wrappers.
- Standardized data processing and IDB caching for all CRUD operations in Journals.
- Updated testing page with comprehensive V3 CUD test buttons.
Refactor how is ensured in badge objects when fetched from the API and cached locally.
Previously, directly injected into results, creating an inconsistency with other data fetching functions. This change centralizes the injection logic within .
- Updated to accept an optional parameter.
- Added logic within the processor to set and on badge objects if they are missing, using the provided context.
- Modified call sites (, , ) to pass the to the function.
This ensures consistent and robust handling of the across badge-related data operations, aligning with the project's data processing patterns.
- Centralize project-wide documentation into a new /documentation directory.
- Remove old, deprecated README guideline files.
- Create a comprehensive AETHER_API_OBJECTS.md file detailing all API data models.
- Added new README.md for the v3 Event Leads module at `src/routes/events/[event_id]/(leads)/README.md`, adapting content from its legacy counterpart.
- Created placeholder README.md files for the main v3 Events module (`src/routes/events/[event_id]/README.md`), the v3 Launcher module (`src/routes/events/[event_id]/(launcher)/README.md`), and the v3 Badges module (`src/routes/events/[event_id]/(badges)/README.md`).
- Moved legacy event modules (`events_badges`, `events_leads`) to `src/routes/legacy/` and renamed them to `events_badges_v2` and `events_leads_v2` respectively.
- Created new directory structures for Svelte 5 and SvelteKit-based event modules under `src/routes/events/[event_id]/`:
- `(launcher)`
- `(badges)`
- `(leads)`
- Updated `src/routes/events_leads/README.md` (now `src/routes/legacy/events_leads_v2/README.md`) with comprehensive documentation for the legacy lead retrieval module, reflecting its features, data model, routing, components, and technical implementation details, incorporating project conventions.
- Updates `GEMINI.md` with a detailed summary of the days debugging session for the v3 Badges page.
- Updates `TODO.md` to mark recent bug fixes as complete and adds new tasks for outstanding issues (e.g., `order_by_li` fix).
- Updates `ARCHITECTURE.md` to reflect the current state of the tech stack (Tailwind v4, component library status).
- Creates `README.md` files for the new v3 Badges module and the legacy Lead Retrieval module to clarify their purpose and status.
This commit addresses several critical issues preventing the new v3 badge search page from functioning correctly.
- **Fixes API Fetch Error:** Resolves a `TypeError: NetworkError` that occurred during badge searches. The error was caused by an incorrect `order_by_li` parameter being sent to the API. The parameter is now temporarily commented out in `ae_events__event_badge.ts` to allow searches to succeed.
- **Fixes Svelte 5 Binding Error:** Implements a defensive initialization for badge search filter properties directly in the `(badges)/badges/+page.svelte` script. This prevents a `props_invalid_value` runtime error by ensuring that bound store values are not `undefined` when the child search component is rendered.
- **Fixes Invalid Attribute Name Error:** Corrects the `onsubmit|preventDefault` syntax in the new badge creation and upload forms. The `preventDefault` logic is now handled inside the respective submit handler functions.
- **Restores Site-wide Styles:** Re-adds the global Skeleton UI CSS imports to `app.css` to fix numerous "unknown utility class" errors (e.g., `preset-tonal-*`) and restores button styles in the legacy leads list.
- **Refactors Badge Search Component:** Updates the `ae_comp__badge_search.svelte` component to use its own reactive props for state management instead of relying on global stores, and removes obsolete/conflicting logic.
This commit addresses several critical issues that were preventing the application from building and rendering correctly.
- **Restores Skeleton UI CSS:** Re-adds the global Skeleton UI CSS imports to `app.css`. This fixes numerous "unknown utility class" errors (e.g., `preset-tonal-secondary`) across the site, allowing components to render with their intended styles again.
- **Fixes Invalid Attribute Name Error:** Corrects the `onsubmit|preventDefault` syntax in the new badge creation and upload forms (`ae_comp__badge_create_form.svelte`, `ae_comp__badge_upload_form.svelte`). The `preventDefault` logic is now handled inside the respective submit handler functions, resolving the Svelte v5 parsing error.
- **Fixes Svelte 5 Binding Error:** Implements a defensive initialization for badge search filter properties directly in the `(badges)/badges/+page.svelte` script. This prevents a `props_invalid_value` runtime error by ensuring that bound store values are not `undefined` when the child search component is rendered.
- **Fixes Invalid CSS Classes:** Replaces incorrect `preset-tonal-*` classes in the legacy `leads_list.svelte` component with standard Tailwind CSS utility classes to prevent further styling-related errors.
- Initialize ds_loaded properties in ae_stores.ts to fix svelte binding error.
- Make db_save_ae_obj_li__ae_obj in core__idb_dexie.ts schema-aware to fix Dexie DataError when saving to tables with non-'id' primary keys.
- Implemented a whitelist for editable fields for the 'event' object type to prevent sending read-only fields in POST/PATCH requests.
- Created a new file to define the editable fields.
- Modified and to use this whitelist.
- Removed the temporary cleaning logic from the event settings page.
- Corrected Svelte 5 to in event settings components.
- Updated Dexie interfaces for badge, badge_template, and device to use string IDs.
Added a summary of the new event settings page to GEMINI.md, documenting the form-based UI, collapsible sections, view toggles, and Svelte 5 reactivity improvements.
This commit introduces form-based UI components for 'mod_badges_json' and 'mod_abstracts_json', and expands the form for 'mod_pres_mgmt_json'. It also adds view toggles to switch between form and raw JSON editing for each of these sections, providing greater flexibility for the user.
This commit introduces a new component for editing basic event fields and adds collapsible sections to the event settings page for better organization. The new fields include code, conference, name, summary, description, timezone, start/end datetimes, and notes.
Added a new 'Development Guidelines' section to GEMINI.md to provide clear instructions on Svelte v5 conventions and the project's 'id' vs 'id_random' usage. This will serve as a reminder to avoid common pitfalls and ensure code consistency.
Corrected event settings components to use Svelte 5 bindable props for two-way data binding. This ensures that changes in child form components (ae_comp__event_settings_form.svelte, ae_comp__event_settings_pres_mgmt_form.svelte) are reactively reflected in the parent page (settings/+page.svelte) and properly handled during save operations.
This commit introduces form-based UI components for editing the 'cfg_json' and 'mod_pres_mgmt_json' fields on the event settings page. This provides a more user-friendly experience than editing the raw JSON directly. It also fixes an issue where the JSON objects were being displayed as '[object Object]'.
This commit introduces a new page at /events/[event_id]/settings that allows users to view and edit the JSON configuration fields for an event. The page provides textareas for each config field and a 'Save' button to persist the changes.
Updated .prettierrc to set 'useTabs' to false and 'tabWidth' to 4, ensuring that Prettier formats code with 4-space indentation instead of tabs, as per user preference.
This commit fixes the event list on the /events page by correcting the data fetching logic. It also adds links to the 'Pres Mgmt', 'Badges', 'Leads', and 'Launcher' modules for each event, and updates the main event link to use the random string ID.
This commit refactors the IndexedDB schema to use the string-based random IDs as primary keys instead of the numeric auto-incrementing IDs. This change affects the 'badge' table and all related components that interact with it. The badge detail page and list component have been updated to use the new primary key for more efficient and consistent data retrieval.
This commit updates the badge list to use the string-based random IDs for badge links. It also updates the badge detail page to correctly fetch the badge data using the random ID from the URL.
This commit fixes several issues with the new badge search functionality. It resolves a 500 Internal Error by moving the Dexie liveQuery into an onMount block to prevent server-side execution. It also fixes a Svelte 5 reactivity issue by using a more explicit subscription pattern for the liveQuery and by correctly accessing state variables in the template. The badge search results are now correctly displayed.
Renamed Tiptap_editor to CodeMirror_wrapper and updated all usages. Renamed the wrapper file to element_codemirror_editor_wrapper.svelte. Fixed a TypeError in the CodeMirror editor buttons by using EditorSelection.range() to correctly create selection ranges.
This commit addresses several issues related to the migration from TipTap to CodeMirror:
- **CodeMirror Initialization Fixes:**
- Resolved 'Unrecognized extension value' errors by refactoring to explicitly import individual CodeMirror extensions instead of relying on . This ensures proper singleton usage and prevents module duplication issues.
- Updated and to utilize these individual extensions.
- **Text Wrapping Enabled:**
- Added to the extensions in and to enable text wrapping in the CodeMirror editors.
- **Content Saving Fixes:**
- Corrected content binding for CodeMirror editor instances in various IDAA components:
- (description, location_text, attend_text)
- (content, notes)
- (content)
- (description, notes)
- (description, notes)
- Ensured that the prop of is correctly bound to the respective state variables in the parent components, and these state variables are initialized with existing content.
- **Save Button Enablement:**
- Fixed an issue in where the Save button was not enabling on content changes. The logic now directly compares the and with the original object's content, ensuring reactivity.
Resolved a critical parsing error in leads_view_lead.svelte due to incorrect Svelte class directive syntax.
Addressed multiple svelte/no-at-html-tags linting errors across the following files to mitigate potential XSS vulnerabilities and improve code safety:
- src/routes/events_leads/exhibit/[slug]/leads_add_scan.svelte
- src/routes/events_leads/exhibit/[slug]/leads_manage.svelte
- src/routes/events_leads/exhibit/[slug]/leads_view_lead.svelte
Replaced {@html} blocks with safer Svelte conditional rendering ({#if}) and direct interpolation ({value}) for static and dynamic content where appropriate. Removed commented-out {@html} tags that were still triggering linting errors.
Migrated the ESLint configuration to the new flat config format ()
and addressed several initial linting errors.
Key changes include:
- Updated ESLint configuration to treat as warnings instead of errors.
- Fixed errors in by declaring and .
- Corrected error in by using instead of an out-of-scope .
- Resolved error in by replacing the undefined directive with the component.
- Addressed errors in by replacing with and with .
- Fixed errors in by importing necessary modules (, , ) and adding missing props (, , , , ).
This commit fixes a bug where presentations, presenters, and files related to a session were not being displayed. The issues were caused by incorrect property names ( suffix) and improper use of Svelte 5 features after a recent refactoring.
This commit introduces a major refactoring of the data processing logic across multiple modules (events, archives, posts, sponsorships) to use a standardized pattern with . This improves consistency and maintainability.
Key changes:
- Replaced module-specific data processing with a generic helper.
- Removed deprecated functions.
- Updated Svelte components to leverage the new Svelte 5 runes, simplifying state management.
- Fixed linting errors and updated test configurations.
- Added .
This commit fixes a bug where the session view was not displaying correctly due to incorrect usage of reactive props.
- In , the prefix was added to the props passed to the component to make them reactive.
- In , the prefix was removed from the variables in the template, as they are already reactive when using Svelte 5 runes.
- The block for was corrected to use the resolved value.
This commit fixes a bug where presentations were not being displayed correctly within the event session view. The issue was caused by an incorrect query that was using the standard session ID to filter presentations, instead of the random session ID.
The liveQuery in has been updated to use the from the derived variable. This ensures that the query correctly fetches all presentations associated with the session.
Reverted changes to legacy launcher links in various components. These links are intentionally structured to work with nginx location aliases and should not be modified to conform to SvelteKit routing.
The session content was not loading in the new Events Launcher because the loaded session data was not being correctly assigned to the events store.
This commit fixes the issue by correctly assigning the loaded session object and its nested properties to the events store.
This commit introduces the initial setup for the `ae_app_3x_llm` branch.
The purpose of this branch is to explore and implement more direct
collaboration with AI Large Language Models (LLMs) for complex code
changes, including the creation of new function libraries and the
modification of existing code.
This initial commit includes:
- GEMINI.md: A file to provide context to the AI about the project.
- TODO.md: A to-do list for upcoming development tasks, created in
collaboration with the AI.
- **Icons:** Standardizing on **Lucide-Svelte**. Avoid legacy Font-Awesome where possible.
### Reactivity Patterns (The "Aether Way")
- **Svelte 5 Runes:** Use `$state`, `$derived`, and `$effect`.
- **Navigation Shield:** Use SvelteKit's `page.url` searchParams as the single source of truth for navigation-driven selection. Sync to global stores only via `untrack()` within effects.
- **Side-Effect Purge:** Keep `liveQuery` observables pure. Do NOT update global stores ($events_slct) inside derived observables to prevent infinite reactivity loops.
- **Dexie LiveQuery:** Use the `$` prefix (e.g., `$lq__obj`) consistently. Results from `liveQuery` are observables.
- **Initialization:** Always initialize reactive state (`$state`) outside of `$props()` destructuring.
### Performance & Data
- **SWR (Stale-While-Revalidate):** Never block SvelteKit `load` functions with API calls for cached data. Let the UI render instantly from IndexedDB and update reactively.
- **Triple-ID Pattern:** Map `id`, `[obj_type]_id`, and `[obj_type]_id_random` consistently.
- **ID Vision:** The backend uses String IDs. Primary keys should use `_id` or `id` (String) mapping to backend `id_random`.
## 🧠 Recent Strategy & Patterns
- **Safe Handover (Native):** Rename `.tmp` to `.file` ONLY after SHA-256 verification in Electron.
- **Envelopes:** API helpers automatically handle the `{data: ...}` envelope returned by the backend.
- **Bootstrap Paradox:** Use unauthenticated bypass (`x-no-account-id: "Nothing to See Here"`) for initial site/domain lookups.
- **Sev-1 Incident Recovery (2026-02-13):** Purged redundant/misplaced headers (`x-aether-api-token`, `Access-Control-Allow-Origin`). Unified all CRUD helpers to standard `/v3/crud/...` paths.
- **Account ID Scavenging:** Core fetch helpers now proactively read `account_id` from `localStorage` (`ae_loc`) if missing from config. This is the mandatory fix for Svelte 5 hydration race conditions where `onMount` triggers API calls before global stores are synced.
- **V3 Event File Mapping (2026-02-19):** Mapped prefixed backend fields (`hosted_file_hash_sha256`, `hosted_file_size`) to flat properties (`hash_sha256`, `file_size`) in the data layer. Required `inc_hosted_file=true` query param for full metadata retrieval.
- **Launcher Location Discovery:** Resolved missing locations in room select by ensuring `load_ae_obj_li__event_location` requests `hidden: 'all'` during background sync and initial load.
## 🤝 Coordination & Continuity
- **Handshake:** Use the `message` tool to notify the Backend Agent of UI/Data requirements.
- **Active Tasks:** Track your progress in `documentation/AGENT_TODO.md`.
- **Reference:** See `README.md` for build/deploy steps and `TODO.md` for project milestones.
The deployment is fully integrated into the unified **Aether Docker Environment** (`aether_container_env`). The application is built inside a clean Docker container using `vite build --mode <env>`, which reads the corresponding `.env.<env>` file for `PUBLIC_` variables.
## Environments
| Environment | Env file | Vite mode | API server |
# Build Docker image and restart container locally
npm run build:docker:dev
npm run build:docker:test
npm run build:docker:prod
# Deploy to remote server (SSH → linode.oneskyit.com → deploy.sh)
npm run deploy:remote:test
npm run deploy:remote:prod
```
### Technical Details
- **Unified Orchestration**: All services (API, UI, Redis) are managed via `~/OSIT_dev/aether_container_env/docker-compose.yml`.
- **Dockerfile**: Multi-stage build. Stage 1 (builder) runs `vite build --mode $BUILD_MODE` using `.env.$BUILD_MODE`. Stage 2 (runtime) creates the final lightweight Node image.
- **Environment Handling**:
-`PUBLIC_` variables are baked into the image at build time via the `.env.<mode>` file.
- Private runtime variables are passed via the Docker Compose `.env` file in `aether_container_env/`.
- **Remote deploy**: `aether_container_env/deploy.sh` handles git pull + Docker build + restart on the server. Triggered via `npm run deploy:remote:*`.
---
# svelte app
## Developing (Local HMR)
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
For the best developer experience with Hot Module Replacement (HMR), start a local development server on your host machine:
```bash
npm run dev
```
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
The local dev server will communicate with the **FastAPI backend running in Docker** (typically via `dev-api.oneskyit.com`). This gives you the speed of local Svelte development with the power of the full Aether stack.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
# Rebuild the node_modules directory and manually install extra Svelte packages
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
## Building and running in production mode
To create an optimised version of the app:
Run the npm update to fix the node_modules directory and package.json
```bash
npm run build
npm list
npm outdated
npm update
npm outdated
npm list
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start":"sirv public --single"
```
## Using TypeScript
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
Other installs?:
Are both still needed? I know at least one of these is. 2024-07-23
- Best Rich Text Editor, made for Svelte Developers with Tiptap
- ShadEditor is "evolving" to be Edra.
- ShadCN is still stuck on Tailwind 3. Waiting to upgrade to Tailwind 4.x. Tailwind 4.x was released in late January 2025. ShadCN is still being worked on as of late March 2025.
The application uses standard SvelteKit `.env` files for build-time configuration (specifically for `PUBLIC_` prefixed variables).
- **`.env.dev`**: Used by `npm run build:docker:dev` and `npm run build:dev`.
- **`.env.test`**: Used by `npm run build:docker:test` and `npm run build:test`.
- **`.env.prod`**: Used by `npm run build:docker:prod` and `npm run build:prod`.
- **`.env.local`**: Used during local development (`npm run dev`).
**Note:** Runtime variables (like private API keys or DB credentials) are managed in the deployment directory's `.env` file and passed to the containers via Docker Compose.
---
## Developing
Start a local development server:
```bash
npm run build
surge public my-project.surge.sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Deployment
```bash
# Build Docker image locally and restart container
npm run build:docker:dev
npm run build:docker:prod
# Deploy to remote server (linode.oneskyit.com)
npm run deploy:remote:test
npm run deploy:remote:prod
```
These commands use the multi-stage **Dockerfile** to build the app in a clean environment and automatically restart the corresponding Docker containers.
- Provide a straightforward policy to keep build caches useful but bounded.
Recommendations
- Primary CI cache: **registry-based buildx cache** (preferred). Use a single cache ref (e.g. `ghcr.io/ORG/REPO:cache`) reused by CI builds.
- Local dev cache: use `--cache-to type=local` for fast iteration but prune periodically.
- Retention: keep registry cache for 30 days by default. Implement registry GC or lifecycle rule to delete older cache blobs.
Rotation strategy
- Option A (simple): CI always writes to the same cache ref `:cache`. Periodically (monthly) run a job to `docker pull` and `docker image rm` older tags if you use date-based tagging.
- Option B (date-tag): CI writes cache to `cache-YYYYMMDD` and a small scheduled job deletes tags older than 30 days.
Pruning commands (developer)
- Remove local build cache older than 72 hours:
```bash
docker builder prune --filter "until=72h" --force
```
- Remove all builder cache (aggressive):
```bash
docker builder prune --all --force
```
CI runner requirements
- `docker` and `docker buildx` available in runner environment.
- Registry credentials provided via CI secrets with permission to push/pull images.
Security & Secrets
- Do not store registry credentials in repo. Use CI secret storage.
This document outlines the overall architecture and key technologies used in the Aether SvelteKit frontend project.
## 1. Project Overview
The Aether project is a Svelte and SvelteKit based application, utilizing Tailwind CSS and Skeleton for styling and UI elements. It serves as the frontend UI/UX for the Aether system, which interacts with a Python FastAPI backend.
## 2. Core Technologies
- **Frontend Framework:** Svelte 5 and SvelteKit v2
- **Routing:** SvelteKit's file-system based routing.
- **Styling:** Tailwind CSS v4
- **UI Component Libraries:**
- Skeleton (Design System, Tailwind Components, Functional Components - being phased out due to conflicts with Tailwind CSS v4)
- Flowbite (Tailwind Components)
- Custom Components (a growing library of `ae_comp__*` and `element_*` components)
The Aether SvelteKit frontend runs in a standard web browser in almost all cases. The Electron native app is a **very specialized exception** with a narrow, specific purpose.
### 7.1. Standard Browser (Default)
All Aether modules run in a regular browser (Chrome, Chromium, Firefox, Safari). This includes:
- Badge printing — `window.print()` works well across Chrome, Chromium, and Firefox. Chrome is recommended for onsite badge printing stations.
- All CRUD operations, event management, journals, IDAA, reports, etc.
The Electron app (`aether_app_native_electron/`) exists **solely** to support the **Events Presentation Management Launcher**. It provides OS-level capabilities that a browser sandbox cannot:
- Control of third-party presentation software (PowerPoint, Keynote, LibreOffice Impress)
- Local filesystem access for slide file management
- Hardware telemetry for connected devices
**What Electron is NOT used for:**
- Badge printing (browser works fine)
- Any other Aether module
- Any general-purpose Aether functionality
The bridge is exposed as `window.aetherNative` (set by Electron's preload script). All code that calls `window.aetherNative` should degrade gracefully when it is `undefined` (i.e., in a normal browser). See: `src/lib/electron/electron_relay.ts`.
**When to assume Electron is available:** Only inside the `/events/[event_id]/(launcher)/` route group, and only when the page was loaded from the native app.
## 8. IndexedDB LiveQuery Usage
-`lq__xyz_obj`: Used for general read-only access to liveQuery results.
-`lqw__xyz_obj`: Used for forms and binding values, representing a writable snapshot of liveQuery results.
- **Note:** Care must be taken when binding to `lqw__xyz_obj` to manage updates and potential conflicts with the underlying liveQuery.
This document outlines the key data structures and their properties used within the Aether SvelteKit frontend project. It covers object properties, field definitions, and how data is managed.
## 1. Object Properties and Fields
### 1.1. Core Standard Fields
These fields are expected to be present in most Aether objects, providing a consistent base structure.
-`id`: Primary key for an object (internal use, often a UUID).
-`id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
-`<object_type>_id_random`: Specific random ID for an object (e.g., `person_id_random`).
-`code`: Short, unique identifier.
-`name`: Display name.
-`enable`: Boolean for active/inactive status.
-`hide`: Boolean for visibility.
-`priority`: Numeric value for ordering.
-`sort`: Numeric value for ordering.
-`group`: Categorization string.
-`notes`: General notes/comments.
-`created_on`: Timestamp of creation.
-`updated_on`: Timestamp of last update.
### 1.2. Special Use Fields
Fields with specific purposes or conditional usage across different object types.
-`for_type`: Indicates the type of object this object is linked to (e.g., 'account', 'event').
-`for_id`: The ID of the object this object is linked to.
-`archive_on`: Timestamp indicating when an object was archived.
-`passcode`: A password or access code associated with an object.
-`external_id`: An identifier from an external system.
### 1.3. Configuration and JSON Fields
Fields designed to store structured data in JSON format.
-`cfg_json`: Configuration data for an object, stored as a JSON string.
-`data_json`: General purpose data for an object, stored as a JSON string.
-`linked_li_json`: A list of linked items, stored as a JSON string.
### 1.4. Special Generated Fields (Client-side)
These fields are generated on the client-side, primarily for facilitating UI logic, such as sorting. They are not typically stored in the backend database.
-`tmp_sort_1`: Temporary sort field 1.
-`tmp_sort_2`: Temporary sort field 2.
-`tmp_sort_3`: Temporary sort field 3.
### 1.5. Future Standard Fields
A list of potential future standard fields, often prefixed with `obj_`. These are conceptual and represent planned expansions to the data model.
- **Specific Sorting (e.g., for time-based events):** `type > start_date/time > code or name`
## 3. Data Storage Mechanisms
### 3.1. Local Storage
Used for client-side persistence of various application states and configurations.
-`api`: Stores API-related settings and tokens.
-`app`: Stores global application settings and preferences.
-`core`: Stores settings and data specific to core modules.
-`<module>`: Stores settings and data specific to extended modules (e.g., `journals`, `events`).
-`<custom>`: Stores settings and data specific to custom modules (e.g., `idaa`).
### 3.2. IndexedDB (Dexie.js)
Used for more structured client-side data storage, often for caching, offline capabilities, and larger datasets.
-`ae_core_db`: The primary Dexie database instance for core application data.
-`<module>`: Module-specific database instances (e.g., `db_journals` for journal data).
-`<custom>`: Custom module-specific database instances (none currently defined, but reserved for future use).
### 3.3. IndexedDB LiveQuery Usage
Dexie's `liveQuery` is used to provide reactive data streams from IndexedDB.
-`lq__xyz_obj`: Represents a read-only liveQuery result for a single object.
-`lqw__xyz_obj`: Represents a writable liveQuery result, typically used for forms and data binding.
- **Note:** When using `lqw__xyz_obj`, developers must carefully manage updates to avoid conflicts with the underlying liveQuery and ensure data integrity.
- **Style:** Strictly `snake_case` for all function and variable names.
- **Deprecated:** `camelCase` should be refactored to `snake_case`.
- **Prefixes:**
-`load_ae_obj_id__<object_type>`: For loading a single Aether object by ID.
-`load_ae_obj_li__<object_type>`: For loading a list of Aether objects.
-`create_ae_obj__<object_type>`: For creating an Aether object.
-`update_ae_obj__<object_type>`: For updating an Aether object.
-`delete_ae_obj_id__<object_type>`: For deleting an Aether object by ID.
-`db_save_ae_obj_li__<object_type>`: For saving a list of Aether objects to IndexedDB.
-`db_update_ae_obj_id__<object_type>`: For updating an Aether object in IndexedDB.
-`process_ae_obj__<object_type>_props`: For module-specific data transformation functions.
- **Deprecated:** Ambiguous `handle_` prefixes should be replaced with more descriptive `snake_case` names (e.g., `handle_submit_form` -> `submit_form`).
## 4. Object and Property Naming
- **Singularity:** Use singular nouns for objects and properties (e.g., `example.id`, not `examples.id`).
- **IDs:**
-`id`: Primary key for an object (internal use, often a UUID).
-`<object_type>_id`: Specific ID for an object (e.g., `person_id`).
-`<object_type>_id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
To ensure instant page transitions and a high-performance feel, the Aether platform utilizes a **Non-Blocking Load Pattern** (also known as Stale-While-Revalidate or SWR). This pattern leverages Dexie's `liveQuery` for reactive UI and SvelteKit's `load` functions for background data synchronization.
## 🚀 The Core Principle
**Never block the `load` function with API calls if the data is already being observed by a `liveQuery`.**
The page should render *instantly* using cached data from IndexedDB. Fresh data from the API should settle in the background and update the UI automatically via reactivity.
---
## ❌ Anti-Pattern (Blocking)
This pattern causes a "white screen" or "frozen UI" while the browser waits for the API response.
```typescript
// +page.ts
exportasyncfunctionload({params,parent}){
constdata=awaitparent();
constevent_id=params.event_id;
// BAD: This blocks the navigation until the API responds.
This pattern completes the navigation immediately.
```typescript
// +page.ts
exportasyncfunctionload({params,parent}){
constdata=awaitparent();
constevent_id=params.event_id;
if(browser){
// GOOD: Fire and forget.
// This function updates IndexedDB in the background.
events_func.load_ae_obj_id__event({
event_id: event_id,
try_cache: true
});
}
returndata;// Navigation completes instantly
}
```
```svelte
<!-- +page.svelte -->
<scriptlang="ts">
import{liveQuery}from'dexie';
import{db_events}from'$lib/ae_events/db_events';
// UI reacts automatically when the background task finishes.
letlq__event_obj=$derived(
liveQuery(()=>db_events.event.get(event_id))
);
</script>
{#if$lq__event_obj}
<h1>{$lq__event_obj.name}</h1>
{:else}
<p>Loading...</p>
{/if}
```
---
## 🛠️ When to use Await
Use `await` in `load` functions ONLY for:
1.**Critical Auth Checks:** If you must verify a session before even showing a layout.
2.**Parent Data:**`const data = await parent();` is necessary to build the context.
3.**Server-Side Rendering (SSR):** If the data *must* be present in the initial HTML for SEO (rare for Aether feature modules).
## 📈 Performance Gains
By adopting this pattern across the Events module, we achieved:
-**~200-500ms reduction** in perceived page load time.
-**Elimination of waterfalls** (sequential API calls).
-**Better offline support**, as the UI is always ready to show what's in the local cache.
---
## Svelte 5 Runes + liveQuery: Critical Patterns
These rules apply to all Svelte 5 runes-mode components (the entire Aether frontend). Violations here are a common source of subtle reactivity bugs and unnecessary re-renders.
### Rule 1: Use `$derived.by()` when liveQuery depends on a reactive value
**The problem:**`$derived(liveQuery(callback))` looks like it should re-run when a store value inside `callback` changes. It does NOT. Svelte tracks reactive dependencies synchronously during the expression evaluation. The `liveQuery` callback is called later inside Dexie's async context — Svelte's tracking is already finished. The dependency is never registered.
```svelte
<!-- ❌ WRONG: $events_slct.event_session_id is read inside the async callback.
Svelte never tracks it. The liveQuery is created once and never recreates
**Rule of thumb:** If the liveQuery result changes based on a reactive value (store property, `$state`, `$props`), always use `$derived.by()`. Reserve `$derived(liveQuery(...))` only for liveQueries that watch a table broadly and don't filter by a reactive value.
---
### Rule 2: Keep liveQuery closures pure (data-only)
**The problem:** Writing to a Svelte store inside a liveQuery callback runs inside Dexie's async transaction context. Svelte's reactive tracking is undefined there. The write may fire at unpredictable times and create hard-to-debug reactivity loops.
```svelte
<!-- ❌ WRONG: Store side-effect inside liveQuery async callback. -->
### Rule 3: Use cheap equality guards in `$effect` before writing to stores
Every store write in a `$effect` triggers downstream reactivity. Always guard with a comparison before writing. The cost of the comparison is always less than the cost of spurious re-renders.
**For single objects** — compare `id` + `updated_on` (O(1)):
**Never use `JSON.stringify` for equality.** It serializes the full object tree on every reactive cycle and is O(total serialized bytes).
---
### Rule 4: Always use `untrack()` when writing to stores inside `$effect`
Without `untrack()`, reading a store to check its current value inside `$effect` registers it as a dependency — the effect re-runs whenever it writes, creating an infinite loop.
**Never hardcode `log_lvl: 2` in a call-site or override `log_lvl` inside a function body.** The parameter default exists so callers can control verbosity. Overriding it forces debug logging regardless of what the caller passed.
| Anonymous | `anonymous` | Default — not signed in |
> **Note on Public vs Authenticated:** `public` is a *site-wide* unlock (anyone with the passcode). `authenticated` verifies a *specific identity*. In the hierarchy, public outranks authenticated because it implies broader site access.
---
## `$ae_loc` Store — Permission Flags
`$ae_loc` is a `persisted()` store (backed by localStorage). Key fields:
```typescript
$ae_loc.access_type// string: current access type ('anonymous', 'trusted', etc.)
// Cumulative boolean flags (true = "you have AT LEAST this level")
$ae_loc.anonymous_access// always true
$ae_loc.authenticated_access// true from authenticated and above
$ae_loc.public_access// true from public and above
$ae_loc.trusted_access// true from trusted and above ← most-used gate
$ae_loc.administrator_access// true from administrator and above
$ae_loc.manager_access// true from manager and above
$ae_loc.super_access// true only at super
// Exclusive check flags (true = "you are EXACTLY this level")
$ae_loc.trusted_check// true only if access_type === 'trusted'
$ae_loc.administrator_check// etc.
// (rarely needed — prefer the _access flags)
// Behavior flags
$ae_loc.edit_mode// boolean — user preference, see below
$ae_loc.adv_mode// boolean — advanced mode toggle
```
### Additional intermediate levels (in permission checks, not in hierarchy order)
`support`, `assistant`, `verified`, `provisional` — appear in `_access` flags but are not part of the canonical `access_level_order`. Treat as internal/intermediate.
---
## Edit Mode — Critical Rules
`$ae_loc.edit_mode` is a **user preference**, not a permission level.
**Rules that must never be broken:**
1.**Components must never write to `$ae_loc.edit_mode`** — only the system menu toggle and sign-out/permission-drop handlers may change it.
2. Edit mode is only available to `trusted` and above in 95% of modules (the toggle is hidden from lower-access users).
3. Edit mode persists across navigation — it is NOT reset by page loads or component mounts.
4. Sign-out and permission drops to below `authenticated` should reset `edit_mode` to `false`.
> **Background:** A bug was fixed (2026-02-27) where `ae_comp__badge_obj_view.svelte` was writing `$ae_loc.edit_mode = false` in a data-loading `$effect`, silently overriding the user's preference on every navigation to the badge print page.
---
## Authentication Methods
| Method | Grants | Used For |
| --- | --- | --- |
| Site passcode (`site_access_code_kv`) | `trusted`, `public`, or `authenticated` | Onsite staff and event attendees |
| AE Username + Password | `trusted` and above | Staff with AE accounts |
| Novi UUID | `authenticated` | IDAA members (Novi membership system) |
Passcodes are stored per-level in `$ae_loc.site_access_code_kv`:
This document is a recipe book. Copy these patterns directly. Deviate only when a component's specific purpose genuinely requires it — and document why in a comment.
---
## 1. Hero Card
*Used for: session identity, presenter identity, location identity — the top-of-page "Is this the right one?" card.*
**Rule:** Never set `bg-*` or `text-*` color classes on `<Modal>`. Let the Flowbite component + active theme handle it. Only structural layout classes (`shadow-md`, `relative`, `flex`, etc.) belong on the `class` prop if needed.
---
## 12. Muted / Secondary Text
Replace all `text-gray-*` patterns with opacity wrappers on inherited text color:
| Icon with visible adjacent text | `aria-hidden="true"` on icon, text provides meaning |
| Icon-only button (no visible text) | `aria-label="Description"` on the `<button>` |
| Icon used as bullet point | `aria-hidden="true"` on icon |
**Never use `<i>` tags.** Always `<span class="fas ...">`.
---
## 16. Native `<select>` Dark Mode
Browser-native `<select>` and `<option>` elements **cannot be reliably styled** with Tailwind `dark:` utilities — the browser controls `<option>` rendering and ignores most CSS overrides. This causes the "light on light hover" bug in dark mode.
**Fix — add `color-scheme` directive to force OS-level dark styling:**
```svelte
<script>
import { ae_loc } from '$lib/ae_core/ae_stores';
</script>
<!-- Forces browser to render the select widget in dark/light OS mode matching your theme -->
**Why this works:** `color-scheme: dark` instructs the browser to use its native dark-mode widget rendering (dark `<select>`, dark `<option>` backgrounds). It's the only cross-browser mechanism that affects `<option>` hover colors.
**Alternative — replace with custom Skeleton/Flowbite component** if you need full styling control (e.g., color-coded options, icons). Native `<select>` is acceptable for simple purpose dropdowns with the `color-scheme` fix above.
**Store reference:** `$ae_loc.dark_mode` — boolean, set by the theme engine in `ae_stores.ts`.
**ALL IDAA content is PRIVATE. Authentication is required for ALL modules.**
IDAA serves a sensitive population — physicians in addiction recovery. Content exposure to the public is a **severe security failure** and a violation of member trust.
- A previous AI agent accidentally exposed IDAA Bulletin Board content publicly. This must never happen again.
- Every route, component, and API call in this module must enforce authentication.
- When in doubt: **it's private**.
**Required access level:**`trusted_access` or higher for all IDAA content.
---
## What IDAA Is
IDAA is a private membership organization for physicians in recovery. They use the Aether platform for:
- A private document archive (historical materials, meeting records)
- A members-only bulletin board (community posts and discussion)
- A searchable directory of in-person and virtual recovery meetings
- Video conferencing (Jitsi-based)
IDAA's Aether instance is embedded as an **iframe inside their existing Novi-powered website** (`idaa.org`). Novi is their external Association Management System (AMS) — it handles membership records and authentication. Aether receives the member context via URL parameters on iframe load.
---
## Architecture: Composite Module
IDAA is **not a standalone module** — it is a **composition of three existing Aether modules**, access-gated and branded for the IDAA client.
│ │ ├── ae_idaa_comp__event_obj_id_edit.svelte # Meeting edit form (active)
│ │ └── [event_id]/
│ │ ├── +page.svelte # Meeting detail page — renders view OR edit based on session flag
│ │ └── +page.ts
│ └── video_conferences/ # Jitsi video conference integration
└── jitsi_reports/ # External Jitsi reporting
```
> **Note:** Recovery Meetings has **two UI entry points**:
> 1. **Modal pattern** (primary list flow) — list, view, and edit components live at `recovery_meetings/`
> level, toggled via `$idaa_sess.recovery_meetings` session flags (`show__modal_view`, `show__modal_edit`).
> 2. **Direct page** (`[event_id]/+page.svelte`) — navigating to `/idaa/recovery_meetings/<id>` renders
> the same view/edit components gated by `$idaa_sess.recovery_meetings.edit__event_obj`.
>
> Both patterns use `ae_idaa_comp__event_obj_id_edit.svelte`. The edit form clears **both**
> `show__modal_edit` and `edit__event_obj` on save/cancel so it works correctly from either entry point.
---
## Authentication: Novi UUID System
IDAA members do not log in through Aether — they log in through Novi (idaa.org), and Novi passes their identity to the Aether iframe via URL parameters.
### URL Parameters (on iframe load)
```
?uuid=<36-char-uuid>
&iframe=true
&key=<site-access-key>
```
> **Security note (2026-03-09):** The iframe HTML files previously also passed `email` and `full_name`
> via URL params. These were unverifiable claims that could be spoofed via URL. They have been removed.
> The SvelteKit layout now fetches verified identity directly from the Novi API.
> See "Iframe Integration" → "Novi UUID Verification Flow" below.
### Verification Flow (`(idaa)/+layout.svelte`)
When a `uuid` param is present in the URL, the layout performs an **async Novi API call** to verify:
1. The UUID actually exists in Novi's system (prevents fake/crafted UUIDs)
2. Gets verified name and email directly from Novi — these can't be forged via URL
"novi_api_root_url":"https://www.idaa.org/api",// optional, this is the default
"novi_admin_li":["uuid-1","uuid-2"],
"novi_trusted_li":["uuid-3","uuid-4"],
"novi_idaa_group_guid_li":["group-uuid"]// Jitsi moderators only
}
```
## Novi API Integration — How We Use It
This section documents the exact way Aether uses the Novi API for the IDAA integration so future maintainers can recreate the flow.
- **Purpose:** Verify a Novi-provided `uuid` received via iframe URL parameters, obtain a verified name/email from Novi, and upgrade Aether permissions for that session when appropriate.
- **All-or-nothing policy:** If the Novi API key is not configured or the verification call fails, the Novi-based access path is denied. The layout explicitly prevents child routes from rendering while verification is in-flight to avoid flashing "Access Denied".
### Verification Flow (implementation)
1. The IDAA iframe loads Aether pages with a `?uuid=<uuid>&iframe=true` param.
2. When the `uuid` param is present the IDAA layout performs an authenticated GET against the Novi customers endpoint:
```js
// simplified
fetch(`${api_root_url}/customers/${uuid}`,{
method:'GET',
headers:{'Authorization':`Basic ${api_key}`}
})
```
3. On success the layout uses the returned JSON to build a display name and normalized email, then writes these values to the IDAA store and marks verification success.
4. The layout then determines a target Novi permission level (`authenticated`, `trusted`, `administrator`) by checking configured UUID lists (`novi_trusted_li`, `novi_admin_li`) and upgrades the Aether session only if the Novi-derived level is higher than the current global level.
5. The layout also resets a few IDAA-specific query defaults (BB filters, etc.) to safe values after verification.
### Key `site_cfg_json` fields and where they are used
- **`novi_idaa_api_key`**: Base64-encoded Basic auth token provided by Novi. Required for the verification request. Accessed in code as `$ae_loc.site_cfg_json.novi_idaa_api_key` and passed in the `Authorization: Basic <key>` header. If missing, Novi-based access is denied.
- **`novi_api_root_url`**: Optional API root (defaults to `https://www.idaa.org/api`). Used to form the verification URL.
- **`novi_admin_li`**: Array of UUIDs treated as administrators for IDAA. Merged into `$idaa_loc.novi_admin_li` during layout initialization and used to set `administrator` level.
- **`novi_trusted_li`**: Array of UUIDs treated as trusted members. Merged into `$idaa_loc.novi_trusted_li` and used to set `trusted` level.
- **`novi_jitsi_mod_li` / `novi_idaa_group_guid_li`**: Lists used to map Jitsi moderator privileges and group GUIDs (where applicable).
- **`novi_bb_base_url`**: (optional) Base URL used to build links for Bulletin Board notification emails.
- **Email config values** (`noreply_email`, `noreply_name`, `admin_email`, `admin_name`): used by functions that send notification emails (BB posts, comments, recovery meetings).
-`$idaa_loc.novi_full_name` — display name built from Novi fields
-`$idaa_loc.novi_verified` — boolean flag indicating successful verification
-`$idaa_loc.novi_admin_li`, `$idaa_loc.novi_trusted_li` — merged lists from site config
These fields are read elsewhere in the IDAA UI to enable flows for verified users (for example: creating meetings, posting comments, or auto-populating contact info in notifications).
### Where in the codebase this runs (examples)
- The Novak UUID verification and permission-upgrade logic is implemented in the IDAA layout: [src/routes/idaa/(idaa)/+layout.svelte](src/routes/idaa/(idaa)/+layout.svelte).
- UI elements that permit actions for verified Novi users or trusted members check these values. Example: the "Create New Meeting" button allows creation when either the session has `trusted_access` or a `novi_uuid` is present — see [src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_qry.svelte](src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_qry.svelte).
### Security notes and operational guidance
- The previous implementation leaked `email` and `full_name` via URL params — this was removed because those values are unauthenticated and can be spoofed.
- The API key is sensitive — keep it only in site config and do not expose it in client-side code or public repositories.
- The verification request uses Basic auth with the provided `novi_idaa_api_key` (already Base64-encoded by Novi) — treat the token like a password.
- If Novi changes their customer API shape, update the layout parsing (display name/email normalization) and this documentation.
If you need a compact checklist for re-creating this flow in another integration, ask and I will add a small runbook with exact request/response field mappings.
### Permission Levels (Ascending)
| Level | Condition | Access |
|---|---|---|
| Anonymous | No UUID, unrecognized UUID, or verification failure | No access |
| Authenticated | UUID verified against Novi API | View own content, limited actions |
| Trusted | Verified UUID in `novi_trusted_li` | Full member access to all IDAA content |
| Administrator | Verified UUID in `novi_admin_li` | Full access + edit/manage |
`novi_trusted_li` and `novi_admin_li` are managed in Aether site config (not in Novi directly).
### Permission Upgrade Rule
```
// RULE: Only UPGRADE to Novi-based permissions, NEVER downgrade.
// If a user has a higher global Aether role (site manager, super),
// their global role is preserved and not overwritten by Novi auth.
```
This ensures that OSIT staff with `super` or `manager` roles retain full access regardless of Novi UUID status.
### Non-Novi Sign-in Paths (unaffected)
- **User/Pass or Auth Link:** No `uuid` in URL → layout Novi block does not run
- **Shared Passcode:** No `uuid` in URL → layout Novi block does not run
### Access Gate (`(idaa)/+layout.svelte`)
The inner layout blocks ALL rendering if the user is not authorized:
- Replies inherit the parent post's visibility rules
### Post Visibility / Archival Filter
Posts with `archive_on` set to a past date are **automatically hidden** from all queries. This is enforced at the component level via a LiveQuery filter:
```typescript
// This filter is REQUIRED — do not remove it
filter((x)=>!x.archive_on||archiveDate>now)
```
Archived posts are soft-deleted — they remain in the database for audit purposes but are not shown to members.
Most recent first (sorted `updated_on DESC`).
### Database (Dexie)
```
db_posts.post — Posts (threads)
db_posts.comment — Post comments (linked by post_id)
**Types:**`ae_Event` (standard event type, filtered for meeting context)
Recovery Meetings reuses the Aether Events object to represent AA recovery meetings. These are NOT conferences — they are regular ongoing meetings (weekly, monthly, etc.) available to IDAA members.
### Search Filters
Members can filter meetings by:
- **Fulltext search** — name, location
- **Physical** — in-person meetings
- **Virtual** — online meetings (Zoom, Google Meet, etc.)
- **Meeting type** — specific meeting format categories
Search is debounced (250ms) and uses the standard Aether SWR pattern.
### Edit Form — Sections and Key Fields
The edit form (`ae_idaa_comp__event_obj_id_edit.svelte`) is organized into these sections.
All fields map directly to the `ae_Event` object; none are IDAA-specific custom fields.
**Rich text fields** all use `AE_Comp_Editor_TipTap` with separate `*_new_html` state variables
(not bound to `$idaa_slct.event_obj` directly) to track change state for the save-button logic.
**Zoom URL auto-generation:** Triggered by `$idaa_trig = 'update_zoom_full_url'`. An `$effect`
reconstructs `attend_json.zoom.full_url` from domain + meeting_id + passcode_enc whenever
the Meeting ID, Passcode, Encrypted Passcode, or Domain fields change.
**Recurring text auto-generation:** If `recurring_text` is blank or contains the `*gen*` prefix,
the submit handler generates a human-readable string (e.g., `*gen* weekly: Monday, Wednesday at 7:00 PM America/Chicago`).
Members can opt into a custom text via "Add More Details?" (admin/trusted only).
**Contact 1 lock:** Contact 1 name and email default to the logged-in Novi member's identity
(`$idaa_loc.novi_full_name`, `$idaa_loc.novi_email`). They are `readonly` unless the user
explicitly unlocks them via confirm dialog (or has administrator access).
### Jitsi Integration
Some virtual meetings are hosted via Jitsi. Members with a Jitsi moderator UUID (`novi_jitsi_mod_li`) have elevated permissions in video sessions.
### Edit Form — Implementation Notes (v2)
- The v2 edit form uses a `<style>` block with `@apply`. Tailwind v4 requires
`@reference "../../../../app.css";` at the top of any component `<style>` block that uses `@apply`.
- The country subdivision lookup list (`lu_country_subdivision_list`) contains duplicate entries —
specifically Puerto Rico (`PR`) has two rows with `code = '-'`. The `{#each}` key must use
the array index (`i`) rather than `sub.code` to avoid a Svelte `each_key_duplicate` error.
The duplicate entries are a **backend data quality issue** that should be cleaned up in the DB.
### Demo / Test IDs
No dedicated IDAA recovery meeting demo records — uses the standard Event demo record for dev:
- Event: `pjrcghqwert` (id: 1) "Demo One Sky IT Conference"
---
## Module 4: Video Conferences (Jitsi)
**Route:**`/idaa/video_conferences/`
Embeds Jitsi video conferences directly in the IDAA module. Separate from Recovery Meetings — this is for IDAA board meetings or special sessions, not regular AA meetings.
Moderation permissions are controlled by `novi_jitsi_mod_li` in the IDAA store.
---
## State Management (`ae_idaa_stores.ts`)
Four stores manage all IDAA state:
### `idaa_loc` (localStorage — persistent across sessions)
Stores Novi auth context and per-submodule query settings:
```typescript
{
novi_uuid: string|null// Member UUID (set on verification success)
novi_email: string|null// Verified email from Novi API
novi_full_name: string|null// Verified name from Novi API
novi_verified: boolean// true after successful Novi API verification
novi_admin_li: string[]// Admin UUID list (from site config)
novi_trusted_li: string[]// Trusted member UUID list
novi_jitsi_mod_li: string[]// Jitsi moderator UUIDs
**SvelteKit layout** (`(idaa)/+layout.svelte`): Calls `GET /customers/{uuid}` on the Novi API using the `novi_idaa_api_key` from `site_cfg_json`. Sets verified name/email in `$idaa_loc` and grants permissions. Shows a "Verifying identity..." spinner during the async call.
**Jitsi page** (`video_conferences/+page.svelte`): Checks `$idaa_loc.novi_verified` in `fetch_novi_data()`. If the layout already verified the user, it reuses `$idaa_loc.novi_email` / `$idaa_loc.novi_full_name` and skips the duplicate member details API call. The group moderator check (`get_novi_group_moderators`) always runs — it is Jitsi-specific.
### ⚠️ Iframe CSS Conflicts (Bootstrap v3)
When `$ae_loc.iframe = true`, the root layout (`+layout.svelte`) injects two external stylesheets from Novi's CDN:
This document outlines key insights and strategies developed during Svelte 5 (with runes mode) refactoring and bug-fixing tasks. It specifically addresses common pitfalls and effective patterns for an AI agent working with Svelte.
## 1. Async/Await in Svelte (Runes Mode)
The most frequent source of errors has been related to asynchronous operations and the `await` keyword.
- **Rule:** Any function or block containing `await`_must_ be explicitly marked `async`.
- This applies to callbacks within promise chains (`.then()`, `.catch()`, `.finally()`), DOM event handlers (`onclick`, `onchange`), and Svelte lifecycle functions (`onMount`).
- **Common Error:** `Cannot use keyword 'await' outside an async function`. This happens when `await` is used in a function/block that is not `async`.
- **Solution:** Ensure the surrounding function or the callback itself is declared `async`.
- The correct pattern for asynchronous navigation using `goto` in Svelte 5 is `await goto(await resolve(path), options);`.
-`import { resolve } from '$app/paths';` is crucial when using `resolve()`. A missing import will lead to `no-undef` errors for `resolve`.
-`resolve(path)` is crucial to ensure the path is correctly resolved before navigation, especially in universal applications.
- The `await` before `goto` is necessary if subsequent code depends on the navigation completing or if `invalidateAll` is used.
- **Clearing Stale Caches:** When encountering confusing linting or build errors that don't seem to match the current code, especially after significant refactorings or dependency changes, always try running `npm run clean` to clear stale build artifacts and then re-lint/re-build.
- **Refactoring Promise Chains:**
- When converting `.then().catch().finally()` chains to `async/await` structure, encapsulate the asynchronous operation within a `try...catch...finally` block.
- **Incorrect:**
```javascript
someAsyncFunc()
.then(() => { await anotherAsyncFunc(); }) // ERROR: .then() callback is not async
.catch(() => { /* ... */ })
.finally(() => { await lastAsyncFunc(); }); // ERROR: .finally() callback is not async
```
- **Correct:**
```javascript
async () => {
try {
const result = await someAsyncFunc();
await anotherAsyncFunc(result);
} catch (error) {
console.error(error);
} finally {
await lastAsyncFunc();
}
};
```
- **Note:** If the entire handler (e.g., `onclick`) is already `async`, you can `await` the promise directly within it.
## 2. Reactive Declarations & Scoping
Svelte 5's runes mode introduces new ways of managing reactivity, and understanding variable lifecycles is key.
- **`$state` for Reactive Variables:**
- Variables that are expected to trigger re-renders when their values change, or whose changes need to be observed, should be declared with `$state`.
- **Common Error:** Warnings like "This reference only captures the initial value of `data`. Did you mean to reference it inside a closure instead?" or "Variable `x` is updated, but is not declared with `$state(...)`."
- **Solution:** Declare the variable using `$state(initialValue)`.
- **Context for `data` prop:** When a `data` prop (from a SvelteKit `load` function) is accessed outside of reactive declarations (like `$effect` or event handlers), Svelte might warn that it only captures its initial value. To ensure reactivity or to correctly process it, use it within `$effect` or derive a `$state` variable from it.
- **Function Scoping and Redeclaration:**
- **Common Error:** `Identifier 'function_name' has already been declared`. This occurs when functions with the same name are defined in overlapping scopes.
- **Solution:**
- If a function is only needed within a specific block (e.g., an `onMount` callback), define it _inside_ that block to limit its scope.
- If a function is truly global and needs to be accessible from templates and multiple `onMount` blocks, define it once outside of any `onMount` and ensure it doesn't conflict with other definitions.
- Be mindful of helper functions that might be implicitly pulled into global scope by the Svelte compiler if not correctly encapsulated.
## 3. `replace` Tool Usage Strategy (Critical for AI)
My efficiency heavily relies on the `replace` tool, and precision is paramount.
- **Exact `old_string` Matching:**
- The `old_string` parameter _must_ precisely match the target text in the file, including all whitespace, indentation, newlines, and comments. Even a single character difference will cause the tool to fail with "0 occurrences found".
- For multi-line replacements, always copy the exact block from the `read_file` output.
- **Contextual Specificity:**
- Avoid generic `old_string` patterns (e.g., `onclick={() => {`) if there are many such occurrences in a file. Instead, expand the `old_string` to include enough surrounding unique context (e.g., the entire button element or parent `div`) to ensure it matches only one instance (`expected_replacements: 1`).
- **Iterative Refinement:**
- For complex refactorings involving multiple changes in a file, perform changes in small, atomic steps.
- **Always `read_file` before each `replace` operation.** This ensures the `old_string` is based on the absolute latest content of the file, preventing mismatches due to previous modifications or unexpected formatting.
- After each `replace` operation, immediately run `npm run build` (or `npm run lint`) to validate the change and catch new errors early. This is crucial for catching cascading issues introduced by partial refactorings.
## 4. Error Debugging Workflow
- **Prioritize Compiler/Build Errors:** These are blocking issues that prevent the application from running. Address them first.
- **Analyze Error Messages:** Read the full error message carefully, including line numbers, and look for keywords (e.g., `await`, `async`, `declared`, `undefined`).
- **Consult Svelte Documentation:** The Svelte compiler often provides helpful links (`https://svelte.dev/e/js_parse_error`) which should be a first point of reference if the error is unfamiliar.
- **Re-read File Content:** If a `replace` operation fails or produces unexpected results, immediately use `read_file` to verify the exact state of the file before attempting another change.
Svelte 5 enforces strict contracts for bound properties (`bind:prop`). If a component expects a property to have a fallback/default value, you cannot bind `undefined` to it.
- **The Error:** `Uncaught Svelte error: props_invalid_value. Cannot do bind:prop={undefined} when prop has a fallback value`.
- **The Cause:** Attempting to bind a variable that is currently `undefined` to a component prop that has a default value (e.g., `let { prop = false } = $props()`). Svelte cannot determine whether to use the bound `undefined` or the component's internal default, so it throws an error.
- **Data Fetching:** If the data comes from an asynchronous source (API, DB), ensure the object properties have default values *immediately* upon assignment, even before the data is fully populated.
# Aether API V3 Frontend Integration Guide (Svelte/TypeScript)
This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints.
---
## 1. Authentication and Security (Mandatory)
V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Requests require two levels of validation.
### A. The "Entry Ticket" (API Key)
**Mandatory for all requests.** identifies the application or client.
***Header:** `x-aether-api-key: <your_app_key>`
***Status Code:** `403 Forbidden` if missing or invalid.
### B. The "Visa" (Account Context)
Required for any non-public data (Journals, Badges, Users, etc.).
1.**Standard Access**: Provide the `x-account-id` (the random string ID).
***Header:**`x-account-id: <account_id>`
2.**Administrative Bypass**: For authorized scripts needing global access.
***Header:**`x-no-account-id: bypass`
3.**Token Access**: Provide a **JWT** in the query string.
***Query Param:**`?jwt=<token>`
> [!CAUTION]
> **UNSUPPORTED HEADERS:** The header `x-aether-api-token` is **NOT recognized** by the V3 API. If you send it, the backend will treat you as a guest and block access to private data.
---
## 2. Bootstrapping (The FQDN Handshake)
When the frontend first loads and doesn't know the `account_id`, it performs a "handshake" using its domain name.
* Returns 200 + a list containing the `account_id` (random string ID) and `site_id` (random string ID).
*** デザイン Choice:** If the domain is not found, it returns **200 OK with an empty list `[]`**. It is NOT a 404.
---
## 3. Standard CRUD Patterns
### A. GET by ID
Used when the ID is known.
***Endpoint:** `GET /v3/crud/{obj_type}/{id}`
***Security:** Returns 403 if the record doesn't belong to your `x-account-id`.
### B. POST Search
The primary way to retrieve data.
***Endpoint:** `POST /v3/crud/{obj_type}/search`
***Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects.
### C. POST Create / PATCH Update
Modify data in the system.
***Endpoints:**
*`POST /v3/crud/{obj_type}/`
*`PATCH /v3/crud/{obj_type}/{id}`
***Strict Mode (Default):** The API validates your payload against the Pydantic model. If you send fields that do not exist in the model, the database might return a 400 "Unknown column" error.
***Permissive Mode (Header):** To allow the frontend to send "extra" fields (like local UI state) without causing errors, use the following header:
***Header:**`x-ae-ignore-extra-fields: true`
***Behavior:** When set to `true`, the backend will automatically strip any fields from the payload that are not defined in the object's model before attempting to save to the database.
### D. ID Fields in Responses (Vision ID Convention)
> [!IMPORTANT]
> **V3 responses always use random string IDs — never database integers.**
All V3 responses — `POST` create, `GET` single, `GET` list, search, and `PATCH` update — contain:
| Field | Type | Use |
| :--- | :--- | :--- |
| `{obj_type}_id` | `string` | **Primary public ID.** Use this for subsequent `PATCH` calls and UI routing. |
| `{obj_type}_id_random` | `string` | Legacy alias. Same value as `{obj_type}_id`. Present for backward compat only. |
> **Note on `_id_random` suffix:** The `{obj_type}_id_random` field is a legacy artifact from the pre-Vision model. Once you confirm `{obj_type}_id` is a random string (length 11–22), you do not need `_id_random` as a fallback. New code should only read `{obj_type}_id`.
---
## 4. V3 Uniform Lookup System
The V3 Lookup system provides a hierarchical, deduplicated interface for standardized reference tables (Countries, Timezones, etc.). It supports global defaults, account-level overrides, and object-level overrides, with optional site-specific whitelisting.
### How the hierarchy works
Each lookup table (`lu_v3_country`, `lu_v3_time_zone`, etc.) can hold multiple rows for the same logical item at different scopes:
| Account override | set | `NULL` / `NULL` | Global default |
| Object override | set | set | Account override + Global default |
The API uses `ROW_NUMBER() PARTITION BY group` to collapse all rows for the same item down to the single highest-priority winner before returning results. **`group` is the identity key** — it is what makes two rows "the same item competing for priority."
> [!IMPORTANT]
> **The `group` field is not a display label.** It is the deduplication key. Each lookup type uses a different natural key for `group`:
> For `time_zone`, `group` and `name` must always be identical — there is no concept of "override all US timezones as a group." Each timezone is its own identity.
### A. List Lookups
Retrieve the deduplicated, ranked list for a lookup type.
*`include_disabled` (Optional): `true` includes shadowed/disabled records (useful for admin views).
**Frontend keying:** Always key Svelte `{#each}` blocks on `group`, not `id` or `name`. `group` is guaranteed unique in the response. Keying on `id` will break if an account override wins (different `id`, same logical item).
***Usage:** Use when you have an external code (e.g., ISO `"US"`) and need the full Aether record. Scans `name`, `group`, and other identity fields.
### C. Site Whitelist Policy
To restrict which lookup items appear for a specific site, add a `lookup_policy` to `site.cfg_json`:
```json
{
"lookup_policy":{
"country":["US","CA","GB"],
"time_zone":["America/New_York","US/Eastern"]
}
}
```
> **Whitelist values must match the `group` field** — i.e., the natural key for that type (ISO code for country, IANA name for time zone). Using a display name will silently return no results for that item.
### D. Adding and managing client overrides
When a client needs a customized label or wants to hide/reorder lookup items, create override records rather than modifying global defaults.
**Rules:**
1.**Never modify global default rows** (`account_id = NULL`). Those are shared across all accounts. Any change there affects every client.
2.**Set `group` to the exact same value as the global default row** for the item you are overriding. If `group` doesn't match, the override creates a new item instead of replacing the existing one.
3.**Set `account_id`** to the client's account ID. Leave `for_type` / `for_id` null unless the override is specific to a single object (e.g., one site).
**Example — rename "US/Eastern" for one account:**
(42,'US/Eastern','Eastern Time (Client Label)','US/Eastern',1,1,50);
```
The `name_override` field is the display label the frontend should prefer when set. `group = 'US/Eastern'` ensures this row competes with — and wins over — the global default in the `PARTITION BY group` deduplication.
**To disable an item for one account** (hide it from their dropdowns):
```sql
INSERTINTOlu_v3_time_zone
(account_id,name,`group`,enable)
VALUES
(42,'US/Samoa','US/Samoa',0);
```
Setting `enable = 0` on an account-scoped row shadows the global default for that account only.
**To remove a client override** (revert to global default):
Simply delete the row where `account_id = <client>` and `group = '<item>'`. The global default row is unaffected and immediately resumes winning.
### E. Adding new global lookup items
When seeding new lookup data (e.g., adding timezones in bulk):
1. Set `group = name` for every row (for `time_zone`). This is a hard invariant — if `group` is set to a regional label like `"United States"` instead of the timezone name, the entire group collapses to a single winner and all but one entry disappear from the API response.
2. Set `account_id = NULL` and `for_type = NULL` / `for_id = NULL` for global defaults.
3. After seeding, verify with:
```sql
-- Should return 0 rows; any result means multiple items will collapse into one
SELECT `group`, COUNT(*) AS cnt
FROM lu_v3_time_zone
WHERE account_id IS NULL
GROUP BY `group`
HAVING cnt > 1;
```
---
## 5. Event File Data Retrieval (Hosted Files)
Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File itself is a metadata record for binary content (files), which is accessed via separate Action endpoints (e.g., `/v3/action/hosted_file/download`). This API endpoint provides metadata about the associated hosted file. To retrieve this additional metadata:
1. **Top-Level Convenience Fields:** The response will include top-level fields for commonly needed hosted file data. These are populated directly from the SQL view via JOINs.
* `hosted_file_hash_sha256` (string)
* `hosted_file_subdirectory_path` (string)
* `hosted_file_content_type` (string)
* `hosted_file_size` (string - in bytes)
2. **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc.
These helper endpoints let the frontend request small server-side transformations without uploading new blobs. They return a newly-created `hosted_file` metadata object on success.
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`. Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize. Returns 400 on failure.
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`.
- Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize.
- For longer-running clips you can schedule the job in the background by adding `?background=true`. When scheduled the API returns `202 Accepted` and the clip runs asynchronously on the server; check the returned `hosted_file` record later via the standard V3 `hosted_file` endpoints.
- Returns 400 on synchronous failure; returns 202 when scheduled successfully.
Frontend guidance:
- Call these routes with the same `link_to_type` / `link_to_id` you plan to associate the resulting hosted_file with — the server resolves random IDs for you.
- After a successful response, use the V3 `hosted_file` action endpoints (download/delete) to manage or retrieve the new file.
- These endpoints run synchronously and can take time for large inputs; for heavy or batch workloads use a queued job pattern instead.
- These endpoints may take time for large inputs. Prefer using `?background=true` to schedule work and receive a `202 Accepted` response for async processing. For heavy or batch workloads use a queued job pattern instead.
---
## 7. User Actions (`/v3/action/user/`)
Stateful user account operations that are not standard CRUD. All require `x-aether-api-key`.
> [!IMPORTANT]
> **Migration from legacy `/user/*` routes:** The table below maps each legacy endpoint to its V3 replacement. Run both in parallel during transition; remove legacy routes once traffic logs confirm they are quiet.
- `valid_email` (optional `bool`): if `true`, marks `email_verified = true` on success.
- `inc_user_role_list` (optional query param, default `false`): include role list in the returned user object.
**Response on success:** Full user object (same shape as `GET /v3/crud/user/{id}`).
**Errors:** `400` missing credentials, `403` wrong password or account disabled, `404` user not found.
> **Auth key flow:** Auth keys are one-time-use — the key is cleared from the DB immediately on successful authentication. Request a new one via `GET /v3/action/user/{id}/new_auth_key`.
---
### B. Verify Password
Check a user's current password without changing it.
The returned key can then be passed to `/authenticate` (as `auth_key`) or embedded in a login URL. The user record must have `allow_auth_key = true` for key-based authentication to work.
---
### E. Email Auth Key URL
Generate a new auth key and email a one-time login link to the user's email address.
| `root_url` | `string` | `null` | Base URL the login link is built from. |
| `key_param_name` | `string` | `auth_key` | Query param name used for the auth key in the generated link. |
**Response:** `data: true` on success (email sent). `500` if delivery failed (check account email config and that the user account is enabled with `allow_auth_key = true`).
---
### F. User Lookups via V3 CRUD Search
The three legacy lookup routes (`lookup`, `lookup_email`, `lookup_username`) are replaced by standard V3 CRUD search:
> **Note:** `exhibitor_notes` has HTML tags stripped automatically for clean CSV output.
### Permission Requirement — `leads_api_access`
> [!IMPORTANT]
> This endpoint enforces a **per-exhibit permission flag**. The `event_exhibit` record **must** have `leads_api_access = true` set in the database, OR the caller must have manager-level account access (JWT with `manager: true`).
>
> If `leads_api_access` is `false` or `null` on the exhibit, the API returns:
> ```json
> { "detail": "Access denied: leads API access is not enabled for this exhibit." }
> ```
> **Fix:** Enable the flag on the exhibit record via `PATCH /v3/crud/event_exhibit/{id}` with `{ "leads_api_access": true }`, or set it directly in the database/admin panel.
#### Dual purpose of `leads_api_access`
This flag serves two related but distinct roles:
1. **3rd-party API access (original intent):** Controls whether external systems (exhibitor apps, badge-scanning devices, etc.) are permitted to push or pull lead data for this exhibit via the API.
2. **UI export gate (new):** The frontend should read `leads_api_access` from the exhibit record and use it to show or hide the export/download button. Only render the button when the flag is `true` — this prevents users from triggering a request that will always 403.
The recommended pattern is to fetch the exhibit record first and gate the UI on this field before the user ever sees the export option. The API enforces the same check server-side as a safety net.
This guide explains how to implement real-time communication using the **Aether API V3 WebSocket** protocol. V3 introduces granular routing, strict message schemas, and improved multi-tenant isolation compared to previous versions.
The V3 WebSocket path requires both a `group_id` and a `client_id`. Both are treated as **opaque unique strings** by the backend — no specific format is enforced.
**`group_id`** — identifies the shared channel (e.g., an event ID, a room name, or a Vision ID random string). All clients using the same `group_id` receive group-targeted messages together.
**`client_id`** — uniquely identifies this specific connection. The backend accepts any unique string (UUID, timestamp, Vision ID — no format validation). The **recommended pattern** is a UUID v4 generated once and persisted in `localStorage` so the same identity is reused across page reloads and sessions on that browser.
> Use `ws://` for local development and `wss://` in production (any HTTPS site). The Nginx config must include the Upgrade block — see Section 6.
### B. Authentication
Browsers **cannot** set custom HTTP headers on WebSocket connections. Pass the API Key and account context as **query parameters** instead:
| `sent_at` | string | Auto | ISO 8601 Timestamp. Set by server. |
> **Frontend tip:** Only send `msg_type`, `target`, and whatever content fields you need (`msg`, `cmd`, `payload`, `to_id`). The server enforces `from_id`, `group_id`, and `sent_at` from the connection context, preventing spoofing.
---
## 4. Message Targeting Logic
V3 uses the `target` field to determine which Redis channel to use, ensuring only the intended recipients receive the data.
### A. Group Broadcast
Sends the message to every client connected to the same `group_id`.
```json
{
"msg_type":"msg",
"target":"group",
"msg":"Hello team!"
}
```
### B. Direct Message (DM)
Sends the message to one specific client ID, regardless of their group.
```json
{
"msg_type":"msg",
"target":"direct",
"to_id":"target_client_random_id",
"msg":"Private message just for you."
}
```
### C. System Broadcast
Sends the message to **every** connected client on the platform (use sparingly).
```json
{
"msg_type":"cmd",
"target":"broadcast",
"cmd":"MAINTENANCE_WARNING"
}
```
### D. Echo
Sends the message back only to the sender (useful for testing round-trip latency).
```json
{
"msg_type":"msg",
"target":"echo",
"msg":"Ping!"
}
```
---
## 5. Specialized Message Types
### Commands (`cmd`)
Used for remote control or orchestration.
```json
{
"msg_type":"cmd",
"target":"group",
"cmd":"RELOAD_UI",
"payload":{"force":true}
}
```
### Heartbeats (`heartbeat`)
Keep the connection alive and **refresh presence** in the backend. Should be sent every 30-60 seconds.
- The server intercepts `heartbeat` messages and refreshes the Redis presence TTL (1 hour window) before echoing back.
- Without periodic heartbeats, a client idle for >1 hour may disappear from the presence set even while still connected.
- Use `target: 'echo'` so the server sends the heartbeat straight back — useful for measuring round-trip latency.
```json
{
"msg_type":"heartbeat",
"target":"echo"
}
```
---
## 6. Infrastructure Requirements (Nginx)
Unlike standard REST endpoints, WebSockets require explicit "Upgrade" handling in the Nginx gateway. If you are deploying to a new server, ensure the following block is present in your Nginx configuration:
```nginx
location/v3/ws{
proxy_passhttp://fastapi_backend;
proxy_http_version1.1;
proxy_set_headerUpgrade$http_upgrade;
proxy_set_headerConnection"upgrade";
proxy_set_headerHost$http_host;
proxy_read_timeout2100s;# Match your app's max heartbeat/session time
}
```
---
## 7. Common Pitfalls & Troubleshooting
- **HTTP 404 Errors**: This almost always means Nginx is missing the `location /v3/ws` block and is trying to serve the request as a static file from the disk.
- **HTTP 400 Errors**: Check your `Host` header. Nginx routes requests based on the `server_name` directive. If you connect to an IP or a non-standard hostname (like `localhost`), ensure it is explicitly listed in your Nginx config.
- **Connection Drops**: If the connection drops exactly after 60 seconds, check your Nginx `proxy_read_timeout`. It should be set high (e.g., `2100s`) to allow for long-lived WebSocket sessions.
---
## 8. Migration Guide (V2 to V3)
If you are upgrading from the legacy V2 WebSocket (`/ws/group/...`):
1.**Change the URL**: Prepend `/v3/` to your WebSocket path.
2.**Wrap your JSON**: In V2, you might have sent `{"msg": "hi"}`. In V3, this must be `{"msg_type": "msg", "target": "group", "msg": "hi"}`.
3.**Use unique string IDs**: Both `group_id` and `client_id` in the path are opaque strings — any unique value works (timestamp, UUID, Vision ID random string). Just don't use raw database integer IDs. For `to_id` in direct messages, use whatever `client_id` that target client registered with.
4.**Listen for `msg_type`**: Update your frontend handlers to switch logic based on the `msg_type` field instead of proprietary keys.
The Aether UI is calm, focused, and softly luminous. It must be immediately readable under conference-room lighting, at a glance, by presenters who are nervous and in a hurry. Staff in the Speaker Ready Room need scan-speed identity confirmation. Remote presenters uploading files from home need a clear, unambiguous interface that doesn't waste their time.
Core principles:
- **Identity first.** The user's first question is always *"Am I in the right place?"* Answer it with the hero card — name, time, room — before anything else is shown.
- **Progressive disclosure.** Admin fields (codes, IDs, passcodes) are hidden unless `edit_mode` is active.
- **Theme-aware always.** Zero hardcoded colors. Every background, border, and text color must respond to light/dark mode and the active theme via CSS variables.
- **Transitions, not pops.** Every interactive state change is smoothed with `transition-colors duration-200`.
- **Section 508 / WCAG 2.1 AA** compliance is non-negotiable. Contrast ratios, focus indicators, ARIA labels, and screen-reader regions are required everywhere.
---
## 2. Technical Stack Mandates (2026 Standard)
To maintain codebase health and performance, all new development must adhere to the following stack:
### 🚀 Svelte 5 Runes
- **Mandatory**: Use `$state`, `$derived`, and `$effect`.
- **Snippet pattern**: Use `{@render snippet()}` for reusable UI blocks within components.
- **Avoid**: Legacy `export let` (use `$props()`), `onMount` for reactive derived state (use `$derived` or `$effect`), and `$$slots` (use Snippets).
### 🎨 Tailwind 4 + Skeleton v4
- **Mandatory**: Use `preset-*` classes for interactive elements (e.g., `preset-tonal-primary`).
- **Customization**: Use Tailwind 4 `@theme` blocks for project-wide overrides.
- **URLs**: Skeleton for Svelte for LLMs docs: https://www.skeleton.dev/llms-svelte.txt
### 🔣 Lucide Icons
- **Mandatory**: Use `@lucide/svelte` components (e.g., `<Calendar size="1em" />`).
- **Migration**: Replaced all FontAwesome `fas fa-*` icons in general modules.
- **🚨 Exception: IDAA Module**: The IDAA module **must** retain FontAwesome and Bootstrap classes. It integrates with Novi CMS which relies on these legacy standards. **Do not migrate IDAA icons.**
---
## 3. The AE_Firefly Theme
**App default since 2026-03-06.** Set in `ae_stores.ts` as `theme_name = 'AE_Firefly'`.
Always wrap in `{#if $lq__obj}{...}{:else}...skeleton...{/if}` — **never** show real content structure before data exists.
---
## 7. Dark Mode Rules
- **Never write `dark:` overrides for background or text colors.** The Firefly theme handles both modes through CSS variables. Writing `dark:bg-gray-800` or `dark:text-gray-400` bypasses the theme and breaks if the user switches themes.
- **Exception allowed:** `dark:text-primary-300` and `dark:text-tertiary-300` in info chips are intentional — they reference theme variables that gracefully degrade.
- **Exception allowed:** `dark:border-surface-700` in fine-grained border work when `border-surface-200-800` isn't strong enough.
---
## 8. Accessibility (Section 508 / WCAG 2.1 AA)
| Requirement | Implementation |
|---|---|
| Decorative icons | `aria-hidden="true"` on all icons |
| Async content regions | `role="status" aria-live="polite"` on loading/empty sections |
| Focus indicators | `focus-visible:ring-2 focus-visible:ring-primary-500` on custom interactive elements |
| Interactive dialogs | `aria-haspopup="dialog"` on trigger buttons |
| Form inputs | Visible `<label>` linked via `for` / `id`, or explicit `aria-label` |
| Color-only information | Always pair color coding with icon or text — never color alone |
| Minimum touch target | 44×44px effective hit area for all tap targets |
| Button label + icon | All buttons should include **both a Lucide icon and text label**. Icon-only is acceptable for space-constrained toolbar/header actions (with `title` attribute); text-only is acceptable when layout is extremely tight. The icon+text combination aids non-English-native users who may not read the label fluently. |
---
## 9. Debug Code — Remove Before Committing
These patterns are breakpoint debuggers added during development. **Never commit them:**
```html
<!-- Breakpoint border debugger — REMOVE before commit -->
- **`opacity-*` for muted text**: Use `opacity-60` (secondary) or `opacity-40` (tertiary/hint) instead of `text-gray-*`. This works in any theme and both modes.
- **`/` opacity modifier**: `bg-primary-500/10` is preferred over separate `opacity-10` — it targets only the background.
- **`text-sm leading-relaxed`**: Standard for body-level descriptive text in cards.
- **`tracking-wide uppercase`**: Use for section label/eyebrow text with `opacity-40`.
- **`whitespace-pre-wrap`**: Required for any `<pre>` or `<p>` displaying user-entered multi-line text (preserves breaks without horizontal overflow).
---
## 12. Known Issues & Workarounds
### `btn` + `preset-filled-*` resolves to transparent inside `card` components
**Symptom:** A button using `btn preset-filled-primary` (or any `preset-filled-*`) inside a `card` div renders with `background-color: transparent`, making it invisible against the card surface.
**Root cause:** The Skeleton v4 `btn` class sets a transparent background via a CSS variable chain. When nested inside a `card` element, the `preset-filled-*` class fails to win the specificity battle and the button appears invisible. This affects both light and dark mode.
**Workaround:** Skip `btn` and `preset-filled-*` entirely for buttons inside `card` elements. Use direct Tailwind token classes instead:
**Scope:**`btn` + `preset-*` classes work correctly on standalone buttons (e.g. page headers, nav bars). The issue is specific to the `card` component context. If we migrate away from Skeleton `card`/`btn`, this issue goes away.
## 6. Inline Field Editing — `element_ae_obj_field_editor`
The standard component for single-field inline editing throughout the platform. Wraps a `PATCH /v3/crud/{obj_type}/{obj_id}` call behind a click-to-edit UI that respects `$ae_loc.edit_mode`.
```svelte
import Element_ae_obj_field_editor from '$lib/elements/element_ae_obj_field_editor.svelte';
```
### Basic usage — text field with custom display
Wrap the display content in the default snippet. The component renders it in view mode and swaps in the input on edit.
| `current_value` | — | Required. Bound with `$bindable` — liveQuery updates flow through automatically |
| `allow_null` | `false` | Shows a "Set Null" button in edit mode |
| `display_block` | `false` | Makes the wrapper `display: block` instead of `inline-block` |
| `on_success` | — | Callback after successful PATCH — use to trigger SWR cache refresh |
| `object_reload` | `true` | Triggers internal SWR reload after patch (in addition to `on_success`) |
### Behavior notes
- The edit trigger button is `visibility: hidden` (not `display: none`) when `$ae_loc.edit_mode` is off — this preserves layout so the page doesn't shift when edit mode toggles.
- Optimistic display: draft value is shown immediately after save; cleared once liveQuery confirms the update came back from the DB.
- `on_success` should always call the relevant `load_ae_obj_id__*` function to keep Dexie in sync.
---
## 7. URL Parameters
URL params consumed by the app. Params are read by layouts and applied on mount.
### Global (active on all routes — read by `src/routes/+layout.svelte`)
| Param | Values | Effect |
| --- | --- | --- |
| `iframe` | `true` / `false` | Enables iframe mode — hides the AE system bar for all users by default, suppresses sign-in/passcode UI |
| `show_menu` | `true` | Override: show the AE system bar inside an iframe. Intended for admins/trusted users who need menu access while testing an embed. |
| `hide_menu` | `true` | Explicitly hide the AE system bar outside of iframe mode (e.g. fullscreen kiosk pages). |
| `theme` | theme name | Applies a theme on load, then removes param from URL (no history entry) |
| `theme_mode` | `light` / `dark` | Applies theme mode on load, then removes param from URL |
Dexie's `liveQuery` works well with Svelte 5 runes, but the combination requires a few stable patterns so queries don't get recreated unintentionally and components render correctly on a "cold start" (empty IndexedDB).
- Keep the observable instance stable: wrap `liveQuery` in a stable `$derived` so the observable isn't recreated on every render. Recreate the `liveQuery` only when explicit dependencies change (IDs, filters, or search keys).
```typescript
// stable derived wrapper — only recreated when `id` changes
letlq__obj=$derived(
(()=>{
// capture the dependency(s) in a single stable closure
constid=url_id;
returnliveQuery(async()=>{
if(!id)returnnull;
console.log('[LQ] running for id=',id);
returnawaitdb.table.get(id);
});
})()
);
```
- Use `$derived.by(() => ...)` where available in your runes shim to build queries from a computed set of inputs (IDs list, search params). This preserves a stable observable instance while still reacting to explicit dependency changes.
- Avoid capturing mutable objects or inline expressions in the `liveQuery` closure. If the closure captures a changing reference, the query may be recreated unexpectedly or miss the first write.
## Common Gotchas and Fixes (Why things sometimes need multiple refreshes)
- Cold start (IDB empty) + non-blocking API writes: If you mount a component before data is written to IDB, `liveQuery` may run against an empty DB. The API write will populate IDB later, but sometimes a chain of dependent queries (e.g., presentations -> presenters) won't all rerun in the order you expect. The symptoms you described — session shows after one refresh, presenters only after a second — are consistent with either (a) queries recreated in the wrong order or (b) dependent store values being set only after some subscriptions are already created.
### Critical Discovery (2026-02-26): The "try_cache: false" Bug
**Symptom:** Nested data (e.g., Session → Presentations → Presenters) requires multiple manual refreshes to display on cold-start, even when using blocking loads.
**Root Cause:** Two interconnected issues in nested data loaders:
1.**Disabled caching in nested loads**: Parent loads were passing `try_cache: false` to child loads, meaning presentations and presenters were fetched from API but **never written to IndexedDB**.
2.**Missing microtask yields**: Even when caching was enabled, components would mount and subscribe to liveQuery *before* IndexedDB writes completed, causing race conditions.
1.**Always preserve `try_cache` through nested loads** unless you have a specific reason to disable caching for that operation
2.**Add `await Promise.resolve()` after IndexedDB writes** to ensure Dexie's liveQuery observers fire before the function returns
3.**Block on nested loads with `await Promise.all()`** instead of fire-and-forget `forEach()` when the page needs complete data for first render
Fixes:
- Prefer the "Blocking Loader" when you can: `await` the API call in `+page.ts` so IDB is populated before Svelte mounts.
- If you cannot block, return an `initial_*` object from `+page.ts` and use it as an immediate fallback in your component so the UI renders from that payload while `liveQuery` takes over for subsequent updates. Example from Aether:
- Ensure store IDs are set before subscribers that depend on them are created. Use `untrack()` (or an equivalent non-reactive assignment) to set IDs in stores during initialization so components subscribe to the correct IDs immediately:
```typescript
$effect(()=>{
if(!ae_acct)return;
untrack(()=>{
$events_slct.event_id=url_event_id;
$events_slct.event_session_id=url_session_id;
});
});
```
- When you have chains (presentations depend on session; presenters depend on presentation.person_id), make the dependent liveQuery explicitly wait for the upstream ID and log inside each query to verify the order — adding a small `await Promise.resolve()` or `await 0` inside the `liveQuery` is sometimes useful during debugging to ensure the JS microtask queue has a chance to settle after DB writes.
## Practical Patterns from Aether (Journals & Events)
- Journals: The journaling pages use SWR-style background refreshes but reliably render because either (a) the page `+page.ts` blocks to populate DB for critical views, or (b) components accept `data.initial_*` fallback values until `liveQuery` emits. This hybrid approach avoids the "refresh twice" problem while keeping navigation snappy.
- Sessions / Presentations: The session page demonstrates several best practices:
- Use `url_*` constants (derived from `data.params`) so the `liveQuery` closure captures a stable value instead of the reactive store directly.
- Provide `initial_session_obj` from `+page.ts` as a first-draw fallback to child components.
- Use `$derived.by(() => liveQuery(...))` for presentation lists so the observable instance is stable across renders and recreated only when `event_session_id` or `search` changes.
Example (presentation list pattern):
```typescript
letlq__event_presentation_obj_li=$derived(
liveQuery(async()=>{
if(!url_session_id)return[];
console.log('[LQ] Querying Presentations for Session:',url_session_id);
- Add a small `console.log` inside each `liveQuery` closure to confirm when it runs and what `id` it sees.
- Verify that `+page.ts` either `await`s critical loads or returns `initial_*` payloads for first-render hydration.
- Confirm that dependent store values (selected IDs) are assigned before components subscribe — use `untrack` to prevent extra reactive cycles.
- Ensure your `liveQuery` closures return quickly and do not throw; any exception inside the query can stop updates.
- If a dependent query appears stale, temporarily add `await 0` in the upstream query or an explicit `Promise.resolve()` after the IDB write to force the microtask queue to flush during debugging.
## Summary Recommendations
- Prefer blocking loads for primary views when first-render correctness matters.
- Use `initial_*` fallback data when non-blocking loads are required.
- Wrap `liveQuery` in stable `$derived` instances and only recreate when explicit inputs change.
- Use `untrack` to set selection IDs during initialization to avoid subscribe-order bugs.
- Add targeted logs inside `liveQuery` closures to diagnose ordering and subscription behavior.
These patterns are deliberately conservative — they trade minimal blocking or small explicit fallbacks for predictable first-render behaviour. The Aether app's Journals and Event session pages are working examples of these techniques in practice.
## Examples in this repository
The following files demonstrate stable `liveQuery` usage, `initial_*` fallbacks, and stable `$derived` wrappers used across the Aether app. Inspect these for copy/paste patterns and concrete implementations.
- Event settings example (simple observable): [src/routes/events/[event_id]/settings/+page.svelte](src/routes/events/[event_id]/settings/+page.svelte#L51)
- Badge/detail pages (examples of nested LQ): [src/routes/events/[event_id]/(badges)/badges/+page.svelte](src/routes/events/[event_id]/(badges)/badges/+page.svelte#L66)
Refer to these files when you need concrete code examples to adopt the patterns described above.
## References
This document provides a guide to integrating Svelte (with a focus on Runes) and Dexie.js for building reactive web applications. It covers key concepts and best practices for managing reactivity between Svelte components and the Dexie.js database.
## Svelte 5 Migration Guide
Svelte 5 introduces "runes" as a new way to manage reactivity. This is a major change from previous versions of Svelte, and it's important to understand the breaking changes before migrating.
### Key Breaking Changes
- **`let` is no longer reactive:** In Svelte 4, any `let` variable declared in the top-level scope of a component was automatically reactive. In Svelte 5, you must explicitly declare reactive state using the `$state` rune.
- **`$:` is replaced by `$derived` and `$effect`:** The `$` label is no longer used for reactive statements. Instead, you should use the `$derived` rune for computed values and the `$effect` rune for side effects.
- **`export let` is replaced by `$props`:** Component props are now declared using the `$props` rune, which provides a more flexible and explicit way to define component APIs.
- **Event handling:** The `on:` directive is replaced by event attributes (e.g., `onclick`). Component events are now handled using callback props instead of `createEventDispatcher`.
- **Slots are replaced by snippets:** The `<slot>` element is replaced by the `{#snippet ...}` block, which provides a more powerful and flexible way to pass content to components.
For a complete list of breaking changes, refer to the [Svelte 5 migration guide](https://svelte.dev/docs/svelte/v5-migration-guide).
## Dexie.js Quick Reference
Dexie.js is a lightweight, minimalistic wrapper for IndexedDB that makes it easier to work with client-side databases.
### Key Classes and Methods
- **`Dexie`:** The main class for creating and managing IndexedDB databases.
-`new Dexie(databaseName)`: Creates a new database instance.
-`version(versionNumber).stores({ ... })`: Defines the database schema.
- **`Table`:** Represents an object store (table) in the database.
-`add(item)`: Adds a new item to the table.
-`put(item)`: Adds or updates an item in the table.
-`update(key, changes)`: Updates an existing item.
-`delete(key)`: Deletes an item by its primary key.
-`get(key)`: Retrieves an item by its primary key.
-`where(index)`: Starts a query using an index.
-`toArray()`: Retrieves all items from the table as an array.
- **`Collection`:** Represents a collection of items resulting from a query.
-`toArray()`: Retrieves all items in the collection as an array.
-`first()`: Retrieves the first item in the collection.
-`last()`: Retrieves the last item in the collection.
-`each(callback)`: Iterates over each item in the collection.
-`modify(changes)`: Updates all items in the collection.
-`delete()`: Deletes all items in the collection.
For a complete list of API methods, refer to the [Dexie.js API Reference](https://dexie.org/docs/API-Reference).
## Integrating Svelte Runes and Dexie.js
The combination of Svelte Runes and Dexie.js allows for the creation of highly reactive and efficient web applications.
### The `liveQuery` Function
Dexie.js provides a `liveQuery` function that returns an observable of the query result. This observable can be used to automatically update the UI whenever the data in the database changes.
### Using `liveQuery` with Svelte Runes
To use `liveQuery` with Svelte Runes, you can create a custom readable store that wraps the `liveQuery` observable. This store can then be used in your Svelte components to display and interact with the data.
**1. Create a `liveQuery` store:**
```typescript
import{liveQuery}from'dexie';
import{readable}from'svelte/store';
import{db}from'./db';// Your Dexie database instance
The `createLiveQueryStore` function creates a readable store that automatically updates whenever the data in the `friends` table changes. The `$friends` variable in the component will always contain the latest data from the database.
## Page Load Strategies (Avoiding the "Waterfall")
When loading data for a primary page view (e.g., viewing a specific Journal, Session, or Person), you must choose a synchronization strategy to ensure the UI renders correctly on the first load.
### ❌ The "Fire & Forget" Anti-Pattern (AVOID)
Triggering a background load in `+page.ts` without `await` leads to race conditions.
1.`+page.svelte` mounts immediately.
2.`liveQuery` runs against an empty IndexedDB.
3. API data arrives later and writes to IndexedDB.
4.**Failure:** Svelte 5 + Dexie `liveQuery` may not automatically detect this first "cold start" update without a manual refresh.
### ✅ The "Blocking Loader" Pattern (RECOMMENDED)
Ensure the data is in IndexedDB **before** the component mounts.
1. In `+page.ts`, `await` the API load function.
2. In `+page.svelte`, the `liveQuery` will see the data immediately upon mount.
**Example (+page.ts):**
```typescript
exportasyncfunctionload({params}){
// Blocking await ensures IDB is populated
awaitjournals_func.load_ae_obj_id__journal({
journal_id: params.journal_id,
try_cache: true
});
return{};
}
```
### ✅ The "Hydrate & Subscribe" Pattern (ADVANCED)
If you must use non-blocking loads, you must pass the initial data to the component to "hydrate" the state before the subscription takes over.
1. In `+page.ts`, `await` the load and **return the object**.
2. In `+page.svelte`, use the returned object as a fallback or initial state.
<!-- Use fallback to handle the gap before liveQuery emits -->
{#if$lq__obj||data.initial_obj}
<Viewobject={$lq__obj??data.initial_obj}/>
{/if}
```
## The `untrack()` Reactive-Tracking Trap
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you *need* to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
### Symptom
An effect runs once, reads a store value inside `untrack()`, takes an early-exit path (e.g. "no API key → skip"), and never retries — even after the store value is updated by a background process.
### Real Example (IDAA Novi Verification Bug — 2026-03-25)
The IDAA layout verifies Novi UUIDs. `site_cfg_json` (which contains the Novi API key) was read **inside**`untrack()`:
```typescript
// BUG: site_cfg_json read inside untrack → one-shot, never retries
if(!api_key)return;// exits silently on first load with stale cache
verify_novi_uuid(uuid,api_key,...);
});
});
```
On first load, the Dexie cache returned a stale `site_cfg_json` missing the API key. The effect exited early. The background refresh later updated `$ae_loc.site_cfg_json`, but because `site_cfg_json` was consumed inside `untrack()`, the effect never re-ran.
**Fix:** Move the dependency read **outside**`untrack()`:
```typescript
// FIX: site_cfg_json tracked outside untrack → effect re-runs when it changes
When preparing data for IndexedDB, especially when creating composite sort keys, it is critical to handle `null` or `undefined` values safely to prevent runtime crashes that can interrupt the data synchronization process.
### 1. Safe String Padding
Attempting to call `.toString()` or `.padStart()` on a `null` or `undefined` value will throw a `TypeError`. This is a common pitfall when processing optional fields like `sort` or `group`.
Dexie's `sortBy()` method returns a new array sorted by the specified key. It **ignores** previous `reverse()` calls on the collection. To achieve a descending sort, you must sort first and then reverse the resulting array.
In print, `position: fixed` anchors relative to the `@page` content area, bypassing the entire
ancestor hierarchy (no containing-block height dependency, no overflow-clip interference).
**Future per-template margins:**`print_margin_cfg` is already parsed from `cfg_json`
in `print/+page.svelte`. A dynamic `@page { margin: ... }` injection can be built from
that value when a UI for it exists.
---
### Cross-browser print behavior — IMPORTANT
Verified 2026-03-12 by comparing print-to-PDF output from both browsers across multiple
print dialog settings.
#### `@page { size }` — paper size
| Browser | Save to PDF | Physical printer |
|---|---|---|
| **Firefox** | Paper size locked — cannot change in dialog; CSS `@page { size }` used ✅ | Can select paper size in dialog |
| **Chrome/Chromium** | Paper size locked — cannot change in dialog; uses system default (letter, A4, etc.) ❌ | Can select paper size under "More settings" |
Chrome intentionally does not honor `@page { size }` for Save as PDF. It uses the system
default paper size. This is a Chrome design decision, not a bug in our code.
For actual printing to Epson/Zebra hardware: the printer driver controls paper size from
the loaded badge stock. CSS `@page { size }` is advisory only. Real badge printing is
unaffected by Chrome's behavior.
Use Firefox for accurate print-to-PDF proofing — it produces a correctly-sized PDF that
| **Default** | ❌ Adds URL, date, and page-number headers/footers into the printable area. These eat into the space that `position: fixed; top: 50%` references, making the badge appear off-center or clipped against the footer. |
| **None** | ✅ Correct — badge centered cleanly |
| **Minimum** | ✅ Correct — small margins, badge still centered |
| **Custom (reasonable values)** | ✅ Correct |
The badge content itself is **not** distorted. Verified: Chrome "None" margins on an A4
page produces the badge perfectly horizontally centered (page center 297.5 pts, badge
content center 297.5 pts). The CSS centering logic is correct.
**Staff guidance for Chrome:**
- Set **Margins → None** (or Minimum) in Chrome's print dialog.
- Optionally set paper size to match badge stock under "More settings" when printing to PDF.
- For physical printer: select correct paper size under "More settings".
Firefox users can use "Save to PDF" directly — it just works.
---
## Related Files
| File | Role |
|---|---|
| `ae_events__event_badge_template.ts` | API + IDB functions; `properties_to_save` |
| `db_events.ts` | Dexie schema for `badge_template` table |
| `templates/+page.svelte` | Template list + create/edit/delete UI |
| `templates/ae_comp__badge_template_form.svelte` | Template create/edit form |
| `[badge_id]/ae_comp__badge_obj_view.svelte` | Badge render — consumes template data |
- [x] Wire `style_href` via `<svelte:head>` in print page — done in `print/+page.svelte`; also in `properties_to_save`. (2026-03-18 verified)
- [x] Add `duplex` to `properties_to_save` — done. (2026-03-18 verified)
- [x] Add `duplex`-driven suppression to `badge_back` section — done in `ae_comp__badge_obj_view.svelte`; `show_badge_back` derived from `duplex` field.
- [ ] Make `layout` field drive actual card dimensions in the badge component — currently the Zebra ZC10L layout CSS (`badge_layout_zebra_zc10l_pvc.css`) sets dimensions correctly via `[data-layout="..."]` scoping, but fanfold layouts still use Tailwind defaults. Needs proper CSS for each layout code.
- [ ] Remove dead `exhibitor_info` / `presenter_info` / `staff_info` / `vip_info` / `vote_info``{#if}` blocks from `ae_comp__badge_obj_view.svelte` (if they were carried over from v1)
- [ ] Improve `ae_comp__badge_template_form.svelte` to edit all relevant fields (currently minimal)
The `*_override` fields pattern protects data from being overwritten during scheduled cron syncs from external systems. This is essential because:
1. Staff may need to correct imported data
2. Attendees may be allowed to self-update certain fields (e.g., preferred name, pronouns)
3. External systems often have outdated or incorrect data
4. Changes should persist across multiple sync cycles
### How It Works
**Import Behavior:**
```
External System → Aether API → Populates REGULAR fields only
(never touches *_override fields)
```
**Display Behavior:**
```
UI Display Logic:
1. IF `*_override` field has value → USE IT (highest priority)
2. ELSE IF regular field has value → USE IT (fallback)
3. ELSE → Display placeholder/empty
```
**HTML Rendering (implemented 2026-02-27):**
Certain fields support HTML markup for rich text formatting. When viewing (not editing), these fields use Svelte's `{@html}` directive to render the markup:
| `badge_type` | `badge_type_override` | Badge category label text | Staff only | No |
| `badge_type_code` | `badge_type_code_override` | Badge access level code | Staff only | No |
| `registration_type` | `registration_type_override` | Registration category label text | Staff only | No |
| `registration_type_code` | `registration_type_code_override` | Registration category code | Staff only | No |
> **Note:** `phone`, `phone_override`, `pronouns_override`, `registration_type`, `registration_type_code`, `registration_type_override`, `registration_type_code_override` may need to be confirmed against the DB schema via `ae_describe event_badge` and added to `properties_to_save` in `ae_events__event_badge.ts` if not already present.
### Sync Safety Rules
**Automated Sync (Cron Jobs):**
- ✅ CAN update: All regular fields (`given_name`, `family_name`, `email`, `affiliations`, etc.)
- ❌ CANNOT update: Any `*_override` field
- ❌ CANNOT delete: Any `*_override` value
**Manual Staff Edit:**
- ✅ CAN update: Any field (including overrides)
- ✅ CAN clear: Override fields (reverts to regular field)
**Attendee Self-Service Edit:**
- ✅ CAN update: Only specific override fields (per event config)
**Important:** Aether is **pull-only** — does not push changes back to external systems. This prevents sync conflicts and maintains external systems as the source of truth for base data.
### Sync Behavior
- **Frequency:** Scheduled cron jobs (typically hourly, daily, or on-demand)
- **Method:** Full sync or incremental (depends on external system API)
- Save button → `handle_save_changes()` — only changed fields sent to API
- Cancel button → `handle_cancel_changes()` — reverts to IDB values
- **IMPORTANT:** This component must NEVER write to `$ae_loc.edit_mode` — it uses its own local `edit_mode_active` flag only. (Bug fixed 2026-02-27)
#### Badge Review Form (`ae_comp__badge_review_form.svelte`)
Form-based review (NOT a badge render). Used by the `/review` page.
**Props:**
-`can_edit_fields: string[]` prop controls which fields are editable per user level
-`['*']` = administrator (all fields)
-`is_staff: boolean` prop shows/hides the staff-only fields
- Fields show "(overridden)" label when an override value differs from the base field
**Features (implemented 2026-02-27):**
- **HTML Rendering**: `full_name_override`, `professional_title_override`, `affiliations_override`, and `location_override` fields render HTML markup using `{@html}` directive when viewing (not when editing)
- **Accessibility**: Text enlargement toggle button switches between text-2xl (normal) and text-4xl (enlarged) for improved readability
- **Help Modal**: Flowbite Modal component with 6 help sections (Reviewing Badge, Editing Info, Accessibility, QR Code, Lead Scanning, Assistance)
- **QR Code Display**: Generates QR code using `core_func.js_generate_qr_code()` with badge ID, supports hover zoom and click-to-expand
- **Print Status**: Shows print count, first print datetime, and last print datetime at top of form
- **Local Edit Mode**: Independent `local_edit_active` state (never writes to `$ae_loc.edit_mode`)
- **Save/Cancel**: Only changed fields sent to API; revert button for override fields
**Editable Fields:**
- Pronouns, Full Name, Professional Title, Affiliations, Phone, Location (all with override support)
The review form (`ae_comp__badge_review_form.svelte`) displays:
1.**Print status** ✅ — print count + first/last print timestamps (read-only, hidden if never printed)
2.**QR Code** ✅ — the attendee's badge QR code for scanning at the badge kiosk (for automatic badge search + print flow). Generated using `core_func.js_generate_qr_code()` with `obj_type: 'event_badge'` and badge ID. Supports hover zoom overlay and click-to-expand.
3.**Editable Fields** ✅ — all fields with access-level gating, override support, and HTML rendering for display fields
4.**Options** ✅ (`other_1_code` through `other_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Option X` checkmark display only when value exists
5.**Tickets** ✅ (`ticket_1_code` through `ticket_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Ticket X` checkmark display only when value exists
6.**Accessibility Toggle** ✅ — Font size enlargement button in sticky header (text-2xl ↔ text-4xl)
7.**Help Modal** ✅ — Attendee guidance modal with 6 sections explaining the review process, editing, QR codes, and lead scanning
#### Default Field Permissions (hardcoded for now — Axonius first show, mid-April 2026)
These are hardcoded in `review/+page.svelte` pending connection to `mod_badges_json.edit_permissions`.
**Attendee (passcode-authenticated / anonymous with link):**
Button has `data-testid="badge-print-btn"` and shows loading/done/error states with icon feedback.
### Print Workflow
1.**Pre-Print:** Badge print page (`/print`) shows "Already printed N times" warning in screen-only header if `print_count >= 1`
2.**Record:**`handle_print_badge()` updates `print_count`, `print_last_datetime`, and `print_first_datetime` (first print only) via API before printing
3.**Print:**`window.print()` — standard browser print dialog, wired and working (2026-02-27)
4.**Redirect:** After 1 second, `goto(/events/{id}/badges)` returns to search
5.**Audit:**`print_first_datetime` and `print_last_datetime` visible in Edit Mode debug row
**Browser vs Electron:** Badge printing does NOT require the Electron native app. The standard browser print dialog works well across Chrome, Chromium, and Firefox. The Electron native app is specialized for the **Events Pres Mgmt Launcher only** and should not be assumed available for badge stations.
1. Map `*_random` fields to clean names (`event_badge_id_random` → `event_badge_id`)
2. Set primary `id` field from `event_badge_id`
3. Ensure `event_id` is set (from function parameter if missing)
4. Calculate `tmp_sort_1` and `tmp_sort_2` for efficient sorting
5. Return processed objects
**Critical Fix (2026-02-26):** All CRUD functions now return **processed** data (matches IDB cache) instead of raw API responses. This ensures consistency between function return values and cached data.
---
## Component Architecture
### Route Structure
```
/events/[event_id]/(badges)/badges/
├── +layout.svelte # Layout wrapper (minimal)
├── +page.svelte # Badge list + search
├── ae_comp__badge_search.svelte # Search form + filters
├── ae_comp__badge_obj_li.svelte # Badge list display (results)
> **Note:** The old `[badge_id]/+page.svelte` placeholder was removed (2026-02-27). The name link in the search results list now goes directly to `/print`.
#### Badge Print Page (`/print`)
- Screen-only header (`print:hidden`): "Back to Search" link + "Already printed N times" warning
- Badge rendered via `ae_comp__badge_obj_view` with `is_review_mode={false}`
- **Edit Mode:** Activated by edit button (or `#review` URL hash for future self-service)
- **Condition:** Renders only when BOTH `$lq__event_badge_obj` AND `$lq__event_badge_template_obj` are non-null
- **Form Binding:** Direct `bind:value` on editable fields
- **Dynamic Sizing:** Font size adjusts based on text length
- **Print Preview:** Full badge layout with template
- **Save Handler:** Only sends changed fields to API
- **`data-testid` attributes:** `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input` — use these in tests
---
## Testing Status
### Current Test Coverage
- ✅ Badge list loads (all 6 data integrity tests passing)
- ✅ Badge template list loads and displays
- ✅ Badge template form renders and populates correctly
- ✅ Badge template values persist in edit form
- ✅ Electron bridge compatibility (graceful degradation in browser)
- ✅ Badge field processor handles missing optional fields
- ✅ Badge type filter tests
- ✅ Badge template relationship tests
- ✅ **Attendee workflow test** — navigate → edit professional title → print → return (d1ded2d4)
### Key Test Lessons Learned
**Search API path is FLAT, not nested.**`search_ae_obj` builds `/v3/crud/{obj_type}/search` — always flat regardless of the parent relationship. Mocks must match this:
**List API (GET) is also FLAT with query params.**`get_ae_obj_li` builds `/v3/crud/{obj_type}/?for_obj_id=...` — always flat. Mocks must check `url.includes('/v3/crud/event_badge_template/') && url.includes('for_obj_id')`.
**CSS `input[value*=...]` selectors don't work with Svelte bind:value.** The CSS selector checks the HTML *attribute*; Svelte's `bind:value` sets the DOM *property* only. In Playwright tests, use `page.getByLabel()` or `locator.inputValue()` instead.
**Dexie requires `_random` ID fields.** Badge objects saved to IDB must include:
```typescript
event_badge_id_random: string// Must be present or Dexie skips the object
id_random: string// Also checked
// Error: "Object is missing a valid ID for table 'badge'"
```
All API mock responses in tests need these fields.
**Badge view requires both badge AND template.**`ae_comp__badge_obj_view.svelte` wraps everything in `{#if $lq__event_badge_obj && $lq__event_badge_template_obj}` — if the template isn't loaded, edit/print buttons and the badge itself don't render. Tests must mock the badge template endpoint.
**Badge GET endpoint (single object):**`/v3/crud/event_badge/{id}` (NOT nested under event). Matches `api.get_ae_obj()` which uses the flat path.
**Badge PATCH endpoint (update):**`/v3/crud/event/${event_id}/event_badge/${badge_id}` (nested under event). Matches `api.patch_ae_obj()` which uses the nested path.
**Use `data-testid` for test selectors.** Key buttons have targets: `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input`.
### Remaining Test Issues
None — all current badge tests passing as of 2026-02-26 (f5e98b8c).
---
## Known Issues & Future Enhancements
### Known Issues
1.**Session Cold-Start:** Potential race condition on first load (same as pres mgmt module)
2.**Type Definitions:** Some pre-existing TypeScript errors on external package types (not introduced by badge work)
3.**`person_passcode` not in DB:** Attendee-gated review URL (`?passcode=...`) cannot function until this field is added to the `event_badge` schema. The review page falls back to passcode entry form for non-staff.
4.**Print page CSS:** Badge print rendering and `@page` print styles not yet fine-tuned — expected to need work
5.**`mod_badges_json.edit_permissions` not connected:** Settings UI exists but review page uses hardcoded field defaults
### Implemented (2026-02-27)
- ✅ `window.print()` wired to print button (records count first, then prints, then redirects)
- ✅ Dedicated `/print` page — replaces old `[badge_id]/+page.svelte` placeholder
Both aggregated into `events_func` via `src/lib/ae_events/ae_events_functions.ts`.
---
## Offline / PWA Notes
- All data is stored in `db_events` (Dexie.js) — `exhibit` and `exhibit_tracking` tables
- SWR pattern: IDB cache returned immediately; background API fetch updates IDB and triggers UI refresh
- Search: local IDB first pass (fast), then API revalidation via `search__exhibit_tracking`
-`beforeinstallprompt` event captured at module load time (`src/lib/pwa/pwa_install.svelte.ts`)
— fires within ~1 second of page load, before any Svelte `$effect` runs
- iOS Safari: no native install prompt; shows "Share → Add to Home Screen" instructions instead
---
## Capture Identity
`external_person_id` and `group` on every `event_exhibit_tracking` record record who captured the lead. Resolved at capture time in all three lead capture components (single scanner, multi scanner, manual search):
| Auth type | `kv.type` | Value stored |
| --- | --- | --- |
| Licensed exhibit user | `'licensed'` | Their email address (`kv.key`) |
| Shared exhibit passcode | `'shared'` | `'shared_passcode'` (label — raw passcode is NOT stored) |
| Aether user (admin bypass, no kv) | `undefined` | `$ae_loc.access_type` — e.g. `'trusted'`, `'manager'`, `'super'` |
Leads are never hard-deleted. "Remove Lead" sets `enable = false`. Key behaviors:
- **Leads list** always filters out `enable = false` records (both IDB fast-path and API results) — no flash of removed records
- **QR scanner**: if a previously-removed badge is scanned, the scanner detects it via `existing_leads_map` (IDB) or API fallback search (`search__exhibit_tracking` with `qry_badge_id` + `enabled: 'not_enabled'`) and shows the reenable card instead of an error
- **Lead detail page**: "Remove Lead" button (two-click confirm in header) sets `enable = false` and navigates back. "Restore Lead" card appears at the bottom of the right sidebar when `enable` is falsy.
-`search__exhibit_tracking` supports `qry_badge_id` param (added) and `enabled: 'not_enabled'` to find disabled records for a specific badge + exhibit combination
---
## Known Gaps
### Payment / Stripe
`ae_comp__exhibit_payment.svelte` is a stub. The Stripe integration is not implemented.
The payment tab can be hidden via "Show Payment Tab" in App Settings.
### License Management — Shared Passcode Access
Implemented. The license section in the Manage tab is visible to Aether admins and to anyone
signed in via the shared exhibit passcode (`auth_exhibit_kv[exhibit_id].type === 'shared'`).
Guard in [ae_tab__manage.svelte](src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.svelte):
**Goal:** Make Docker image builds for Aether cache-friendly using BuildKit/buildx and CI registry caching, while keeping local developer caches small and manageable.
Summary
- Implement a BuildKit-friendly multi-stage `Dockerfile` pattern for frontend and API images.
- Add CI `buildx` examples that push/read registry-based cache to avoid local disk bloat.
- Provide cache retention/rotation guidance and developer commands for safe pruning.
Scope
- Repository areas: `aether_container_env/`, root `Dockerfile` (if present), and CI pipeline definitions (Gitea/Drone or other).
- Non-goal: full CI pipeline migration to a new provider. This work provides CI snippets and a PR-ready set of files for your CI team.
- [ ] Add CI step using `docker buildx build` with `--cache-from` and `--cache-to` pointed at a registry cache.
- [ ] Add a scheduled job or registry lifecycle rule to delete old cache images (30 days default).
- [ ] Document required CI secrets and permissions (registry write/read) for the operations team.
- [ ] Run verification builds (dev local with BuildKit; CI runs with cache) and record timings.
Verification
- Local dev: `DOCKER_BUILDKIT=1` build with `--cache-to`/`--cache-from` shows cache hits on second run and faster build time.
- CI: subsequent CI runs log `cache hit` from `buildx` and total build time reduced vs baseline.
- Confirm registry contains `cache` image tags and that rotation job/prune removes old entries.
Notes about Gitea/CI
- Gitea does not include native Actions like GitHub; teams typically use Drone CI, Tekton, or a self-hosted runner that can execute the `docker`/`buildx` CLI.
- The provided `ci_buildx_example.sh` is intentionally provider-agnostic — pasteable into Drone, Jenkins, GitLab CI, or any shell-capable runner.
Risks & Mitigations
- Risk: Unbounded registry cache growth. Mitigation: enforce retention policy and rotation job; prefer a single `cache` tag reused by CI.
- Risk: Developers unfamiliar with BuildKit. Mitigation: examples show simple `DOCKER_BUILDKIT=1` usage and local cache prune commands.
Next steps for the container team
1. Review examples in `aether_container_env/` and adapt the Dockerfile to your runtime constraints (ssl certs, env injection, secrets).
2. Add a CI job using the `ci_buildx_example.sh` snippet; configure registry credentials as secrets.
3. Add a scheduled job to rotate/delete old cache images or configure registry lifecycle rules.
4. Run a before/after benchmark of `time npm run build:prod` inside the build stage to quantify improvement.
**Future:** Auto font scaling using mm/inch units (physical paper stock measurements).
Will likely need to revisit the inch ↔ mm conversion and potentially expose the auto-sizing
logic as adjustable rather than replacing it with px overrides.
---
## Context
The Events Badges module is mostly complete for navigation and search. Two key pieces of
functional UI were needed before the first show:
1. **Badge Review Form** ✅ — `ae_comp__badge_review_form.svelte` now has complete field
rendering, edit inputs gated by access level, save/cancel API calls, and display-only
sections (QR code, print status, option/ticket checkmarks). Also includes accessibility
features (text enlargement) and help modal for attendee guidance.
2. **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 the `ae_comp__badge_obj_view.svelte` render — not the page layout/template structural dimensions.
Read `documentation/MODULE__AE_Events_Badges.md` for full module context before starting.
---
## MANDATORY: Before You Start
1. 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 to `properties_to_save` in
`src/lib/ae_events/ae_events__event_badge.ts` if they are not already saved to IDB.
2. Fields to specifically confirm exist in `event_badge` schema:
- `pronouns`, `pronouns_override`
- `phone`, `phone_override`
- `allow_tracking`
- `agree_to_tc`
- `other_1_code` through `other_8_code` (the "option" fields)
The Aether Events Launcher utilizes an Electron-based "Native Shell" to provide OS-level capabilities that are normally restricted by browser sandboxing. This enables persistent file caching, direct control of presentation software (Keynote, PowerPoint), and hardware telemetry.
### Operational Modes
| Mode | Purpose | File Handling |
| :--- | :--- | :--- |
| **Default** | Standard web browser access. | Direct downloads; no local caching. |
| **Onsite** | Web access on event networks. | Faster polling; browser-based file management. |
- **Wallpaper:** `set_wallpaper({path})` — macOS (AppleScript) + Linux (gsettings).
> **Note:** `update_app` is implemented as a stub — downloads but does not install. Not yet functional for end users.
---
## 6. Launcher Configuration & Management
The Launcher features a standardized, responsive configuration interface designed for onsite technical management.
### 6.1 UI Architecture
- **Tabbed Navigation:** Categorized into System, Sync, and General settings.
- **Section Wrapper (`Launcher_Cfg_Section`):** A shared component providing a consistent header, icon, and responsive grid container.
### 6.2 3-Way State Logic
To manage screen real estate on varying laptop resolutions, all configuration sections utilize a 3-way visibility state:
- **`collapsed`**: Content is hidden.
- **`auto`**: Expanded by default, but automatically closes if another "auto" section is opened.
- **`pinned`**: Expanded and remains open regardless of other section interactions.
### 6.3 Technical Mode (`edit_mode`)
The UI dynamically filters fields based on the user's focus. Enabling Technical Mode (`$ae_loc.edit_mode`) reveals advanced diagnostic and writeable fields.
| **Update** | Current Version Status | Manual Update Paths, URL Overrides |
---
## 7. Implementation Reference (IPC Whitelist)
All functions below are exported from `src/lib/electron/electron_relay.ts` and safely
no-op when `window.aetherNative` is not present (i.e., in browser/non-native mode).
### Config & Info
-`get_device_config()` — Returns hydrated device settings injected by the native shell on startup.
-`get_device_info()` — Returns OS metadata, IP list, hostname, and path placeholders (`[home]`, `[tmp]`).
### File Cache
-`check_hash_file_cache({cache_root, hash, hash_prefix_length?})` — Verifies a file exists in the local hashed cache.
-`download_to_cache({url, cache_root, hash, api_key, account_id, hash_prefix_length?})` — Streams a file download to the hashed cache with SHA-256 integrity check.
-`cleanup_tmp_files({cache_root, max_age_minutes?})` — Removes stale `*.tmp` download artifacts. Default: 1440 min (24h). Called at launcher startup.
> `hash_prefix_length` defaults to `2` throughout. Do not change without coordinating all devices — mismatched values create orphaned cache subdirectories.
### Shell & OS
-`open_folder(path)` — Opens a path in the OS file manager.
-`control_presentation({app, action})` — Slide navigation (`next`/`prev`/`start`/`stop`) for PowerPoint or Keynote via AppleScript.
### System Management (Phase 5)
-`set_wallpaper({path})` — Sets desktop wallpaper. macOS (AppleScript) + Linux (gsettings).
-`window_control({action, value?})` — Electron window management: maximize, minimize, fullscreen, kiosk.
-`set_display_layout({mode, configStr?})` — Mirror or extend displays via displayplacer. macOS only.
-`power_control({action})` — Shutdown, reboot, or sleep the host machine. macOS + Linux.
-`manage_recording({action, options?})` — Aperture capture control (`start`/`stop`/`status`). macOS only.
-`open_external({url, app?})` — Opens a URL in Chrome, Firefox, or the default browser.
-`update_app(args)` — **Stub only.** Downloads but does not install. Not yet functional.
-`list_tools()` — Returns a self-documenting manifest of all available native bridge functions.
### Path Placeholders
All paths passed to native handlers should use tokens rather than hardcoded OS paths:
-`[home]` — Resolved to the user's home directory by the native bridge.
-`[tmp]` — Resolved to the system temporary directory.
### Not Exposed via Relay (intentional)
-`get_seed_config` / `get_jwt` — Exposed in the preload but not relayed to the UI. The JWT and seed are injected into the environment at startup; components should not call these directly.
> **Replaces:** `element_ae_crud.svelte` and `element_ae_crud_v2.svelte`
## 1. Overview
Consolidate the legacy CRUD components into a single, high-performance "Aether Object Field Editor" (v3). This component will be the standard for single-property editing across the platform, fully aligned with the FastAPI V3 CRUD patterns and Svelte 5 Runes.
## 2. Strategic Objectives
- **Consolidation:** Retire `v1` and `v2` components in favor of a single, unified codebase.
- **API Alignment:** Native support for `PATCH /v3/crud/{obj_type}/{obj_id}`.
- **Svelte 5 Runes:** Pure `$props`, `$state`, and `$derived` implementation. No legacy imports.
This review covers all modules and their distinct use cases. Changes must be sequenced to avoid breaking live-production systems (Events Launcher, IDAA).
### Scope of Modules
| Module | Routes | Primary Users | Notes |
|---|---|---|---|
| Core / App Shell | `+layout.svelte`, `e_app_sys_bar.svelte` | All users | Foundation — impacts everything |
Skeleton's `.input`, `.select`, `.textarea` classes do not include dark mode styles. This causes white text on white backgrounds in dark mode. Currently patched with inline `<style>` blocks per-component (see `e_app_sys_bar.svelte` lines 693–707).
**Fix:** Global utility in `app.css` (see Phase 1, Step 1). Once added, remove per-component patches.
> **Note:** All legacy `variant-*` classes have been fully removed from the codebase. Use only `preset-*` classes for all buttons and interactive elements.
### Custom `ae_btn_*` Classes (app.css)
These exist in `app.css` and wrap the `preset-*` system. They are valid but underused. Consider adopting where button groups need reuse.
### Rule: Opacity Over Fixed Colors for Muted Text
```html
<!-- ✅ Theme-aware muted text -->
<spanclass="text-sm opacity-60">Note</span>
<!-- ❌ Fixed muted text — breaks in dark mode -->
<spanclass="text-sm text-gray-500">Note</span>
```
> **Exception:** `text-gray-*` is acceptable in components that intentionally use plain Tailwind grayscale for neutrality (e.g., Journals list cards), as long as `dark:text-gray-*` counterpart is always included.
---
## 8. Card & Layout Patterns
See `documentation/AE__UI_Component_Patterns.md` for the full pattern reference.
**Step 1 — Global form dark mode utility** ✅ Target: `src/app.css`
Add a global utility so Skeleton `.input`, `.select`, `.textarea` classes render correctly in dark mode. This eliminates all per-component `<style>` patches.
**Step 2 — FontAwesome → Lucide in events nav/layout files**
This document outlines the modernization of the Journals module UI in the SvelteKit frontend (`aether_app_sveltekit`). The primary goals are to fully leverage the generic V3 API architecture and introduce high-velocity productivity features for journal management.
**Context:** The backend transition to the generic `api_crud` router is complete. Custom legacy routers have been removed. The frontend must now fully align with this pattern and provide a frictionless user experience.
---
## 2. Core Objectives
### 🎯 Primary Goals
1.**V3 API Verification:** Ensure all CRUD operations utilize the generic `api_crud` endpoints (Verified).
2.**Quick Add UI:** Implement a specialized interface for rapid, friction-free entry creation.
3.**Append/Prepend UI:** Allow users to quickly add text to the beginning or end of existing entries without full edit mode.
- [x] Implement Auto-Save toggle and visual status indicators.
- [x] Extract decryption workflow to non-reactive helper.
- [x]**Standardize Configuration Modals:** Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
- [x]**RESOLVED:** Decryption workflow stability (Fixed via dependency isolation).
- [x]**Style Standardization (2026-03-06):** Full Skeleton v4 `preset-*` class pass across all 17 journal components. See style token table in Lessons Learned below.
- [x]**Dark mode fixes:** Entry content hover, journal view section/description background and text colors.
- [x]**Modal close button:** All 3 config modals use `dismissable={false}` + explicit `<X>` button in header snippet for correct right-aligned placement.
- [x]**Global select padding:** Added `padding-inline: 0.5rem` to `@layer base` in `app.css` (safe — utility `px-*` classes override it where intentional).
- [ ] Solidify E2EE passcode system for Journals and Entries.
- [ ] Audit encryption flow for Quick Added and Imported entries.
- [ ] Integrate Outbound Email sharing.
---
## 🧠 Lessons Learned: Solving the Svelte 5 Reactivity Hang
During the implementation of the Privacy/Decryption toggle and the new Configuration Modals, we encountered critical browser hangs caused by infinite reactivity loops. Here is how we resolved them:
***The Problem:** An effect would read `save_status` or `tmp_entry_obj.content` to decide if it should sync, but the act of syncing would update those same variables, re-triggering the effect.
***The Fix:** Wrap any "check-only" state or store reads in `untrack(() => ... )`. This allows the effect to use the value without becoming a dependency of it. This is **CRITICAL** when initializing local state from props inside an effect.
We have established a unified design language for configuration interfaces and all Journals UI. **Use these as the module template.**
#### Modal Chrome
***Header/Footer:**`bg-orange-100 dark:bg-orange-900` with consistent orange borders.
***Close button:** Always use `dismissable={false}` on the `<Modal>` and add an explicit `<button>` with `<X>` inside the `{#snippet header()}` so placement is fully in our control. The `flex-1` class on the `<h3>` pushes it right.
***Tabs:** Center-aligned `btn btn-sm` with `preset-filled-primary` (active) / `preset-tonal-surface` (inactive).
***Icons:** Every tab and primary action should have a Lucide icon for better scannability.
***Explicit Persistence:** Follow "Edit working copy → Save Changes" pattern to prevent accidental store/API churn.
- Any `bg-{color}-100` dynamic background **must** have a `dark:bg-gray-800` (or similar) override — light shades are unreadable in dark mode.
- Hover states on content areas need both light and dark variants: `hover:bg-blue-100 dark:hover:bg-blue-950`.
- Text locked to `dark:text-gray-900` is almost always wrong — use `dark:text-gray-100`.
### 3. Dexie LiveQuery Subscriptions
***The Problem:** Accessing `liveQuery` observables directly in templates results in `[object Object]` or `undefined` property errors.
***The Mandate:** ALWAYS use the `$` prefix (e.g. `$lq__obj`) when passing or using data from a Dexie `liveQuery`.
### 4. Manual Deep Copying vs. Proxies
Svelte 5 state is backed by Proxies.
***The Problem:** Using `JSON.parse(JSON.stringify(proxy))` can sometimes trigger unexpected behavior or loops when used inside a reactive context.
***The Fix:** Implement a manual `deep_copy` helper or selective property assignment when syncing "Original" vs "Temporary" state. This ensures `orig_entry_obj` is a plain JS object, making the `has_unsaved_changes` check stable.
### 3. Concurrency Locking (`is_processing`)
***The Problem:** Decryption (Async) and Auto-Save (Debounced Async) can fire nearly simultaneously.
***The Fix:** Use a simple `is_processing` boolean flag. If any async workflow is active, block others from starting and prevent the `has_unsaved_changes` derived rune from reporting `true`.
### 4. Comparison Normalization
***The Problem:** Trivial differences (e.g., `null` vs `""` or trailing whitespace) would trigger "unsaved changes" and fire the save loop.
***The Fix:** Use a `normalize()` function in the `has_unsaved_changes` derived rune to trim strings and treat `null/undefined` as empty strings during comparison.
---
## ⚠️ Technical Blocker: The "Decryption-Sync" Loop (Resolved)
### The Issue
The component is suffering from a **Reactive Feedback Loop** between decryption, auto-save, and background IDB refreshes.
1.**Decrypting content** triggers a change in `tmp_entry_obj.content`.
2. The **Auto-Save effect** sees this as a manual user edit and saves to the database.
3.**Dexie LiveQuery** detects the DB update and refreshes the object.
4. The **Sync effect** resets the entry to its encrypted state (from DB).
5. The **Auto-Decryption effect** fires again, starting the loop over.
### What we tried:
***`is_processing` flags:** Attempted to block reactivity during decryption.
***`untrack()`:** Attempted to isolate store updates.
***Reference Sync:** Attempted to update `orig_entry_obj` simultaneously with decryption to fool the change detector.
***Direct Store Updates:** Switching from property assignment to `journals_sess.update()` to fix Svelte 5 notification failures.
### Future Fix Ideas:
1.**Native Svelte 5 State:** Refactor `journals_sess` from a Svelte 4 `Writable` to a class using `$state`.
2.**Logic Extraction:** Move decryption logic into a non-reactive class/helper to isolate side effects from the UI render cycle.
3.**Hash Comparison:** Use content hashes for change detection instead of string comparisons to avoid whitespace/normalization loops.
> **Goal:** Eliminate all dependency on legacy API wrappers (`create_ae_obj_crud`, `get_ae_obj_id_crud`, etc.) and ensure 100% adoption of the V3 Standard (`/v3/crud/...`).
---
## 1. Executive Summary
While the **Journals** and **Identity (User/Account)** modules have been successfully migrated to the V3 architecture, a significant portion of the **Events**, **Sponsorships**, and **IDAA** modules still rely on legacy V1/V2 wrappers. This document serves as the master checklist to reach 100% V3 compliance.
**Why this matters:**
***Security:** V3 enforces strict multi-tenant isolation via JWT.
***Maintenance:** Legacy wrappers in `api.ts` contribute to technical debt and "God Object" anti-patterns.
***Performance:** V3 offers optimized search and partial updates (PATCH) that legacy endpoints lack.
---
## 2. Migration Audit (Findings)
The following files have been identified as using legacy CRUD wrappers.
### 🔴 High Priority: Events Module
The entire `ae_events` library is heavily dependent on legacy `v2` list and `v1` CRUD wrappers.
* Verify the module still loads data (check Network tab for `/v3/` requests).
* Verify saving works (check for 400 Bad Request errors).
## 4. Standard Practices (V3)
### A. Permissive Update Mode
To simplify frontend state management, V3 supports ignoring unknown fields in update payloads.
- **Header:** `x-ae-ignore-extra-fields: true` (Enabled by default in `api_patch_object`).
- **Use Case:** Allows syncing objects that contain read-only metadata (e.g. `created_on`, `_lq_id`) without manual scrubbing.
### B. Structured Error Handling
V3 returns detailed error metadata in the `meta.details` object.
- **Implementation:** Core helpers automatically extract this metadata.
- **FastAPI Fallback:** Standard `{"detail": "..."}` responses are automatically wrapped into the `meta.details` format by the frontend helpers.
---
## 5. Known Pitfalls
### A. The "Integer Trap" (Search Mapping)
**Issue:** The backend automatically maps certain fields (like `account_id`) from string IDs to internal integers.
**Symptom:** Providing a string ID in a search body that the backend maps to an integer can result in **Zero Results** if the underlying view expects a string.
**Final Solution (Body + Header Injection):**
1.**Body:** Inject the raw field name (e.g. `account_id`) into the `search_query.and` array to bypass automatic backend mapping.
2.**Headers:** Pass `headers: { 'x-account-id': ... }` manually to provide context for Auth validation.
3.**Isolation (IDAA):** Due to specific bugs in the IDAA module, it has been temporarily isolated to a legacy V2 search function (`qry_ae_obj_li__event_v2`) using `default_qry_str` for text searching, while the main module continues to use the V3 implementation.
---
## 6. Final Cleanup
Once all checkboxes above are completed:
1. [x] Remove legacy exports from `src/lib/api/api.ts`.
> Use this file to track steps for complex features or bug fixes.
> **Status:** Stable — ongoing development.
## 🚧 Upcoming High Priority
### [Stores] Refactor — Phase 2c (deferred)
Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining:
- [ ]**Phase 2c — Actual separate stores (`ae_auth`, `ae_app`):** Requires touching ~471
`$ae_loc.*` auth-field read sites across 150+ files. Deferred until a Svelte runes migration
of the store layer itself (touching every component anyway makes the callsite sweep cheap).
### [Backend] Join event_location_id onto event_presenter API view
The `event_presenter` object currently has `event_session_id` but not `event_location_id`.
When navigating from the Presenter View to the Launcher, the frontend has to do a secondary
session lookup to discover the location (magic redirect in launcher base `+page.svelte`).
Joining `event_session.event_location_id` into the presenter view/response would let the
frontend pass the location directly in the Launcher URL without the extra lookup.
- Backend: add `event_location_id` (and `event_location_id_random`) to the `event_presenter`
view or API response
- Frontend: add `event_location_id` to `ae_EventPresenter` type and `properties_to_save`;
pass as `events__launcher_id` in `presenter_page_menu.svelte`
### [Launcher] Active features (identified 2026-03-06)
- [x]**Font size cycler (Launcher sidebar):** Font size cycler and light/dark toggle added to new `menu_launcher_controls.svelte` component; wired into `launcher_menu.svelte`. Visibility toggles (All Files / All Sessions) moved to same component and restyled to `preset-tonal-tertiary`. (2026-03-11)
- [x]**Minor Svelte warning:**`slct_event_location_id` in `menu_location_list.svelte` — prop already has `$bindable(null)`; stale comment in file updated. (2026-03-11)
### [Svelte] State reference warnings
- [x]**`svelte-check` fully clean — 0 errors, 0 warnings.** All 42 `state_referenced_locally` warnings fixed (2026-03-11). CSS `@apply`/`@reference` warnings in `ae_idaa_comp__event_obj_id_edit.svelte` also resolved — Tailwind utilities inlined, `<style>` block removed. (2026-03-16)
### [Badges] Remaining badge work before first live event
- **Badge print controls UX polish:** Scott has improvements in mind — TBD next session.
File: `ae_comp__badge_print_controls.svelte`.
### [Badges] Zebra ZC10L Hardware Testing — ~week of 2026-03-16
Scott is renting a Zebra ZC10L for one day to do real-world badge printing tests before
Axonius (mid-April). See `documentation/PROJECT__AE_Events_Zebra_Hardware_Test_Day.md`
for the full checklist and prep plan.
**Pre-test work (do before printer arrives):**
- [x]**Debug outlines are gated** — `print/+page.svelte` prints nothing; outlines live in
`static/ae-print-badge.css` behind `html.debug_outlines` class (toggled by the "Show debug
outlines" checkbox in the controls panel, trusted-only). Won't appear in print unless explicitly
enabled. No action needed. (verified 2026-03-18)
- [ ]**Zebra ZC10L Linux driver** — install CUPS driver package ahead of time; verify card prints
before burning rental time on driver setup. Check Zebra's site for Linux/CUPS driver.
- [x]**`style_href` wired** — `print/+page.svelte` already loads `style_href` via `<svelte:head>`
and it's in `properties_to_save`. (verified 2026-03-18)
- [x]**`duplex=0` hides badge back** — `duplex` is in `properties_to_save`; v2 badge render
gates `{#if show_badge_back}` on `duplex != null && !!duplex`. Set `duplex=0` on the template
to suppress the back section for single-sided PVC. (verified 2026-03-18)
- [ ]**Set up test event + PVC template** in dev DB with `layout: badge_3.5x5.5_pvc`,
`duplex=0`, a few badge records with varied name lengths, HTML in fields, different badge_type_codes.
- [ ]**Test data set:** include edge cases — very long name, HTML markup in name/affiliations,
badge with no affiliations, badge with all ticket/option codes set.
### [Leads] Exhibitor Lead Scanning — IN PROGRESS (demo-ready prep)
Module is substantially built as a PWA (no Electron). Core flow works end-to-end.
Spec: `documentation/PROJECT__AE_Events_Exhibitor_Leads_v3.md` and `_detail.md`.
Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_events__exhibit*.ts`.
**What's working:**
- Exhibit search/landing (`/leads/`) — SWR, local + API search, sort
singleton captures `beforeinstallprompt` (Chrome/Android/desktop) and detects iOS Safari
for manual "Share → Add to Home Screen" instructions. Reusable `element_pwa_install_prompt.svelte`
placed on the Leads Start tab between the feature grid and sign-in. `pwa_install.init()` wired
into root `+layout.svelte`; dismiss persists 7 days via localStorage. svelte-check: 0 errors.
### [DevOps] Remaining deployment items
- [x]**Wire AE_APP_REPLICAS:**`docker-compose.yml` line 147 already has `scale: ${AE_APP_REPLICAS:-1}`. (verified 2026-03-11)
- [x]**Archive ae_env_node_app:** Archived as tar.gz under `~/OSIT_dev/backups/`; old history/docs moved to `~/OSIT_dev/for_reference_only/`. (2026-03-11)
- [x]**Build Optimization:** Current state finalized. Local Gitea instance stood up at `git.dgrzone.com` (Docker, home server) — future: migrate repos from Bitbucket, verify Backblaze/restic backups cover Gitea data. (2026-03-11)
### [General]
- [x]**Temp Cleanup:**`cleanup_tmp_files` wired in `launcher_background_sync.svelte`; called at launcher startup. Confirmed working. (2026-03-11)
- [x]**`window.print()` for badge print button:** Wired in `ae_comp__badge_print_controls.svelte` — increments count, fires `window.print()`, redirects to badge search. (done)
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
## ✅ Completed (2026-03)
- [x]**[Stores] Phase 1 — Dead code cleanup** (`ae_stores.ts`, `ae_events_stores.ts`, `ae_idaa_stores.ts`): removed `ver_idb`, stale comments, `console.log` lines, Stripe button block (zero consumers), personal Novi UUIDs, dead alternatives. Net: −202 lines across 3 files. svelte-check: 0 errors. (2026-03-16)
- [x]**[Stores] Phase 2a — Split defaults into domain sub-files**: `ae_stores__auth_loc_defaults.ts`; `ae_events_stores__badges/launcher/leads/pres_mgmt_defaults.ts`. Spread-merged back into store structs — zero consumer changes. (2026-03-16)
- [x]**[Badges]** Badge print page svelte-check fix: extracted print CSS to `static/ae-print-badge.css`; fixed unclosed `<script>` tag in `print/+page.svelte`. (2026-03-16)
- [x]**[Svelte/Tests]** svelte-check cleanup: fixed `select_ref_badge_type``$state()` declaration; two `<svelte:component>` deprecations in launcher components; `page.evaluate()` two-arg pattern in `badge_print_layout.test.ts`. (2026-03-16)
- [x]**[Launcher]** Hosted file download button `require_auth` prop — added `require_auth?: boolean` (default `true`) to `ae_comp__hosted_files_download_button.svelte`; all existing consumers unchanged. Launcher `launcher_file_cont.svelte` passes `require_auth={false}` so unauthenticated kiosk users can open/download files without being blocked. (2026-03-16)
- [x]**[Security]** `PUBLIC_AE_API_SECRET_KEY` audit complete. Key is `PUBLIC_*` by design (always in client bundle). Highest-risk anonymous path uses limited-permission `PUBLIC_AE_BOOTSTRAP_KEY`. Full server-side migration not justified given JWT + account_id auth layers. Current state acceptable. (2026-03-11)
- [x]**[UX]** Session Expired banner — `ae_auth_error` store wired to API helpers; root layout sets `flag_expired` on 401/403; non-blocking dismissible banner rendered. (2026-03-12)
- [x]**[UX]** Access Denied UI standardized — `element_access_denied.svelte` created; `/core` layout, `/events/settings`, and `/events/badges/review` updated to use it. (2026-03-12)
- [x]**[Build]** Rollup/Vite circular dependency warnings eliminated — `manualChunks` in `vite.config.ts` colocates all `svelte/*` internals into a single `svelte-vendor` chunk, preventing `runtime.js` / `index-client.js` split (~35 warnings gone). (2026-03-11)
- [x]**[Refactor]** `try_cache` audit + sponsorship/event_file/hosted_file SWR alignment — removed vestigial `try_cache` params from `generate_qr_code`, `ae_core_functions` wrappers; added SWR fast/slow path to sponsorship loaders; changed `event_file` and `hosted_file` single-object loader defaults from `false` → `true` for consistency. (2026-03-11)
- [x]**[DevOps]** Frontend + Backend unified into single `aether_container_env` Docker Compose. `ae_app` service live with healthcheck, single exposed port (`AE_APP_NODE_PORT`), internal `ae_api` networking. Deploy scripts in `package.json` both target `../aether_container_env/docker-compose.yml`. (2026-03-10)
- [x]**[DevOps]** `/health` endpoint live at `src/routes/health/+server.ts`. Docker `HEALTHCHECK` uses it. (2026-03-10)
- [x]**[UI]** Dark mode `color-scheme` fix — `html.dark/light { color-scheme }` in `app.css`; all native browser controls now sync to app dark mode. (2026-03-10)
Clean up inconsistent access-denied and session-expired UX across the app:
1.**Session Expired banner** — When the API returns 401/403, show a non-blocking dismissible banner in the root layout rather than silently failing. The `flag_expired` placeholder in the root layout is already wired for this but nothing sets it.
2.**Standardize Access Denied display** — Replace the one-off browser `alert()` in event settings with a proper in-page gate. Create a small reusable component for inline denial cards.
3.**Maintain intentional special cases** — IDAA Novi UUID gate and the Root Site Access Key gate are correct and must not be touched.
---
## 2. Current State Inventory
### Pattern A — Root Layout: Site Access Key Gate ✅ Working, keep as-is
**Verdict:** Works correctly. The minimal styling is intentional (it's a hard site gate). Keep but no code change needed unless you want to polish the styling later.
---
### Pattern B — Root Layout: Session Expired Banner 🔴 Declared, never set
**File:**`src/routes/+layout.svelte` line 63
**Variable:**`let flag_expired: boolean = $state(false)` — **never set anywhere.**
**Intent:** This should show a non-blocking dismissible banner whenever the API returns 401/403, signaling a stale JWT/session.
**Gap:** API helpers detect 401/403 and log diagnostic info but never fire any store event.
-`onMount`: 500ms delay then `goto('/')` if still not `manager_access` (handles slow hydration)
-`{:else}` block: Shows a styled "Access Restricted" card with Lock icon, description, "Return Home" button while waiting/denied
**Verdict:** This pattern is correct and consistent. The `{:else}` visual gate prevents flashing. The delayed redirect is a graceful fallback. **No changes needed.**
---
### Pattern D — IDAA Layout: Novi UUID Gate ✅ Intentionally custom, keep as-is
**File:**`src/routes/idaa/(idaa)/+layout.svelte`
**Logic:** Async POST to `https://www.idaa.org/api` to verify Novi UUID. `novi_verifying` flag prevents Access Denied flash during network round-trip.
**Verdict:** Intentionally custom to IDAA's member verification flow. **Do not standardize or touch this.**
**Verdict:** Contextually appropriate and functional. The "Try Again" button is good UX. This is a prime candidate to be replaced with a reusable component once one exists, but it is not broken.
---
### Pattern G — API Helpers: 401/403 Detection Without UI Feedback 🔴 Gap
**Current behavior:** Logs auth diagnostics to console, returns `false` or `null`. No store event fired.
**Gap:** When a JWT expires mid-session, the user sees requests silently fail (data doesn't load/save) with no explanation. They may think the app broke.
**Fix:** On 401/403, set `ae_auth_error` store → root layout watches it and sets `flag_expired = true`.
---
### Pattern H — Presenter Auth (`auth__person`) — Existing system, no UX issues to fix now
**Anonymous toggle:** Per-event config allows presenters to upload files without signing in
**Verdict:** Auth system is working. The gating UI in the presenter pages is contextually managed. Not in scope for this cleanup. Revisit when building out the Leads feature or a future auth refactor.
---
## 3. Issues Ranked by Priority
| # | Severity | Issue | File | Fix |
|---|---|---|---|---|
| 1 | 🔴 High | API 401/403 silently fails — users have no feedback | `api_*.ts` | Wire to `ae_auth_error` store |
| 2 | 🔴 High | `flag_expired` never set — session expired banner never shows | `+layout.svelte` | Watch `ae_auth_error`, render banner |
| 3 | 🟡 Medium | `alert()` in event settings — ugly, blocking, not idiomatic | `settings/+page.svelte` | Replace with `onMount` gate + reusable component |
| 4 | 🟢 Low | Badge review inline card — not reusing a component | `badges/.../review/+page.svelte` | Replace when `element_access_denied.svelte` is ready |
---
## 4. Design Decisions
### 4a. Session Expired Banner Design
- **Non-blocking top bar** — similar to the existing `is_offline` and `api_unreachable` banners in the root layout
- **Dismissible** — user clicks X to clear; or auto-hides after signing back in
- **Message:** "Your session has expired. Please reload or sign in again." with a Reload button
- **Trigger:** Any 401 or 403 from any of the three API helpers
**Note:** Only import `ae_auth_error` — no other store changes. Do NOT import `ae_auth_error` at module level if the API helpers are used SSR-side. Use a dynamic import or guard with `browser` check if needed.
- **Session expired banner:** Force a 401 by testing with an expired JWT or by calling an API with wrong credentials. Banner should appear. Dismiss should clear it. Reload should reload browser.
- **Event settings gate:** Navigate to `/events/{id}/settings` without `administrator_access`. Should redirect without any `alert()` dialog.
- **Badge review:** Enter a bad passcode — Access Denied card should appear with "Try Again".
- **IDAA, /core, root site key gate:** Verify no regressions.
---
## 8. Risks & Notes
- The API helpers (`api_get_object.ts` etc.) run in a module context that is imported across many components. The `ae_auth_error` store must only be imported/used inside a `browser` guard or dynamically if the helpers are ever called SSR-side. Check `src/lib/ae_core/ae_core__site.ts` (which uses these helpers during SSR hydration) — **do not set the store from SSR context.** Add `if (browser)` before the `ae_auth_error.set(...)` call.
- The root layout sync effect runs `untrack()` to prevent circular store updates. The new `$effect` for `ae_auth_error` must also use `untrack()` to be safe.
-`flag_expired` should NOT permanently gate the UI — it should only show a top banner. The user may have been mid-editing and should not lose their work. The current `flag_denied` full-screen block is for site key access only.
* Sign in and "claim" an assigned license linked to an Exhibitor
* Collect and manage Leads per Exhibitor
* Export Leads data to CSV or XLSX
* Exhibitors will have the link to their Exhibit sent to them or they can use the Exhibit Search to find their Exhibit and sign in with the shared passcode or their assigned licensed user credentials.
---
## Primary Leads Pages/Tabs
1. start - Sign In / Licenses
* Exhibit passcode sign in
* Exhibitor Leads user sign in
* Payment - Leads Payment
2. add - Add (Search/QR)
* Text search
* QR scan
3. leads - Leads List
* List of Attendees (Event Badge ID) linked to Exhibit
* Click to view/edit Exhibit Tracking entry
4. manage - Leads (app and exhibit) Manage
---
## Exhibitor Sign In and Licenses:
* An Exhibit needs to have a "Shared exhibit passcode" for primary sign in.
* A Licensed Exhibit staff person needs to have a License assigned to them. It is important that the email address not be changed per license. Or if they do, be aware of the affects on tracked attendees. They can then sign in with that email address and passcode assigned. And/Or we need to make the email sign in link work as well.
* Once signed in with the Exhibit passcode, they can change it (passcode) and assign Exhibit Leads licenses once they have paid.
* An exhibitor marked as "priority" means they have paid. Only OSIT admins can mark them as paid.
* Should at least claim/assign one initial user license so an Exhibit related staff can sign in (without the shared Exhibit passcode). Then add, update, or remove licenses based on max allowed per Exhitibor.
Payment:
* Not Paid: <span class="fas fa-question text-red-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Waiting for payment
A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode can add/edit/remove icenses.
License:
* full_name
* email
* passcode
---
## Structure Overview
* There are 4 primary tabs for the Event Exhibit Leads module.
* The overall header/footer should hide by default once signed in.
* If not signed in, only the first tab, to start and sign in is shown.
---
## [tab 1] Start / Sign In / Payment
* Only shows when not signed in as Exhibit Licensed Leads User
* Sign in with Exhibit passcode
* This will then allow them to Manage the Exhibit License
* Sign in with Exhibit Licensed user
### [tab section] Payment
* Shows if signed in with Exhibit Passcode and not marked as paid / priority
* Field for license info - event_exhibit.license_li_json
* Use Exhibit passcode first?
* Select number of licenses
* Select number of small/large devices
* Make payment (Stripe)
### [tab section] Licensed Users
* Shows if signed in with Exhibit Passcode and are marked as paid / priority
* Fill in Exhibit staff users per max licenses
## [tab 2] Add - Search / QR Scan
* One button that toggles between showing and hiding the QR add mode? The text search is always shown above or below the QR scan camera image area.
* Search - Essentially the same as the Badge search. Full name, email, affiliations, ID, etc
* QR Scan - Allow for auto add toggle instead of confirming per scan. Allow for manual entire of Badge ID
* The QR scan is basically just using the Event Badge ID encoded in the persons QR code on their badge. In fact it can probably just populate the text search field?
* Must include the Leads licensed user's email address when adding to the Exhibitor Tracking list. Linked using event_exhibit_tracking with the Licensed user's email address.
## [tab 3] Leads - List of Attendee Leads for Exhibitor
* Allow for toggle between showing all per Exhibit and per licensed user based on their email address. Not perfect, but works well enough.
* Allow for easy edit or remove
* Button to Export Data - CSV or XLSX
* Toggle for show/hide Hidden records
* Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first
## [tab 4] Manage / Config
### Exhibit Specific
* Priority/payment toggle - Administrator Access or above
* Max licenses (number) - readonly or edit for Administrator Access or above
* Small devices (number) - readonly or edit for Administrator Access or above
* Large devices (number) - readonly or edit for Administrator Access or above
* Exhibit (shared) Passcode
* Same Exhibit Leads License list component as the Start tab's Licensed Users section
### App Specific
* Show/Hide Payment Tab
* Additional Settings:
* List refresh interval in seconds - default 25 seconds; 1 second to 2 minutes (120000)
* Basic reload/refresh
* Clear Indexed DB
* Clear localStorage
* Auto hide header/footer on sign in - default true
* (?) Turn on iframe mode
* (?) Show or hide additional details - Use "$events_loc.show_details"?
* Can add custom exhibitor_notes and custom responses_json
* Can set Priority (Star/Flag)
* Can set Sort (number)
* Can set Hide (toggle)
---
## App Specific - Leads Module Config Options and Stored Data
* Signed in using Shared Exhibit Passcode
* Can make payment and manage user licenses
* Need to be able to sign in with Shared Passcode and Leads License passcode.
* Signed in using one Leads License slot they were assigned to
* Things will be tied to their email address
* Can add, edit, remove, etc the Leads specific to an Exhibit.
* This list is shared among all Licensed staff for a specific Exhibit. It is recommended they stay toggled to only showing their additions by default.
* Need to be able to sign in with Shared Passcode and Leads License passcode.
* Show and Hide payment tab (override sort of)
* Show and Hide header/footer (override sort of)
* Use iframe mode??
* Light and Dark mode??
* List refresh interval
* Tracking list sorting
* Show and hide hidden records
* Show and hide enabled records (Administrator Access or above)
There are essentially 3 ways to "Sign In":
1. Aether Platform in general using a defined Core User or using a Client Site specific passcode.
2. Exhibit shared passcode for general Exhibitor management. Should only be briefly needed to pay and assign license slots
3. Licensed Leads User using the email address and passcode assigned by the Exhibitor manager or OSIT/client staff person.
---
## First Steps for Leads v3
Create stub directories, pages, and other supporting files for each primary tab, and each primary section within each tab, and for viewing a specific Lead (Exhibit Tracking).
Once we have that looking good, then we can add the functionality. So we are kind of laying out the framework and pre-documenting (commenting) the files as we go.
---
## For Reference if Needed
Known goodish reference version of Leads v2ish:
d1021e28227db6d49b16cc48e324872b1a322da3 November 19, 2025 at 10:45 PM
* Has a search for Exhibits section that allows for searching Exhibits by name or code.
* Results can be sortable. They can be sorted by name, booth number, or last created/updated.
* This is for exhibitors to find their Exhibit and sign in with the shared passcode or their assigned licensed user credentials.
* If not signed in with Administrator Access or above, then only show results for Exhibitors that have been marked as "priority" (paid). Otherwise show all Exhibitors (to admins).
* Has a list of Exhibits with basic info and link to Exhibit Leads management page for each Exhibit. Do not show results by default or if less than 3 characters in search and they are not signed in with Trusted Access or above.
## [exhibit_id page] Exhibit Leads Module
* This is minimalistic. Only the tabs and content needed.
### Header/Footer
* Overall header/footer should hide by default once signed in. Use Aether iframe mode?
* Leads module header should show the Exhibit name and booth number. It should also have a button to toggle the Add Lead tab and Lead List tab. There should also be a button to show the Manage/Config tab for the Exhibit.
Header: [Name and Booth # text] [Add Lead / Lead List toggle] [Manage/Config button]
### Tabs:
I am probably using the term "tab" loosely here. It may just be sections that show/hide based on buttons or whatever. The main thing is to keep the UI simple and not overwhelm the user with too many options at once. The main flow should be to easily add leads and then view/manage those leads. The Manage/Config tab is more for the person managing the Exhibit and should be separate from the lead capture and management interface.
1. Start - Sign In / Licenses / Payment
2. Add - Search/QR
3. Leads - List of Attendee Leads for Exhibitor
4. Manage - Leads (app and exhibit) Manage
### [tab 1] Start / Sign In / Payment
* Only shows when not signed in as Exhibit Licensed Leads User
* Show notification and or message that this is a PWA and can be installed on their device for easier access.
* Sign in with Exhibit passcode
* This will then allow them to Manage the Exhibit License
* Sign in with Exhibit Licensed user
* This will then allow them to Manage the Exhibit License
* Payment - Leads Payment
* Not Paid: <span class="fas fa-question text-red-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Waiting for payment
* This will then allow them to Manage the Exhibit License
* Change to Sign Out once signed in
* Once signed in, show message to the Exhibit person. Allow them to select number of Licenses, make a payment, and manage the Licenses if paid.
* Sign in with Exhibit Licensed user
* This will then allow them to Manage the Exhibit License
* Change to Sign Out once signed in
* Once signed in, show message to the Leads user. A big button should allow them to then start adding leads.
* Payment (Stripe) - Leads Payment
* Only show if signed in with Exhibit "shared" passcode and not marked as paid.
* Licenses (table):
* A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode can add/edit/remove licenses.
* License:
* full_name
* email
* passcode
* Buttons and Inputs:
* Sign in with Exhibit passcode
* Sign in with Exhibit Licensed user
* Payment - Leads Payment
* Add/Edit/Remove Licenses (if signed in with Exhibit passcode or Trusted Access or above)
### [tab 2] Add - Search/QR
* Show only when signed in as Exhibit Licensed Leads User or Trusted Access or above.
* Allow for text search of Attendee Badge ID, QR code, name, email, or affiliations.
* Allow for QR code scan to add Attendee Badge as Lead.
* Once found, show basic Attendee Badge info and button to "Add as Lead".
* If already added as Lead, show message and button to "View Lead".
* Sections:
* Text search
* QR scan
* Results with "Add as Lead" or "View Lead" button
* Buttons and Inputs:
* Text input for search
* Button to trigger search
* Button to trigger QR scan (opens camera and scans QR code on badge)
* Button to "Add as Lead" if Attendee Badge found and not already a Lead
* Button to "View Lead" if Attendee Badge found and already a Lead
Functions needed:
* Search function to find Attendee Badge by Badge ID, QR code, name, email, or affiliations.
* QR code scan function to read QR code and find Attendee Badge.
* Add Lead function to create Exhibit_tracking entry linking Exhibit and Attendee Badge.
### [tab 3] Leads - List of Attendee Leads for Exhibitor
* Allow for toggle between showing all per Exhibit and per licensed user based on their email address. Not perfect, but works well enough.
* Allow for easy edit or remove
* Sections:
* List of Leads with basic info and buttons to Edit or Remove
* Options:
* Filter by Licensed user email address (dropdown of emails that have added leads for this Exhibit)
* Toggle for show/hide Hidden records
* Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first
* Buttons and Inputs:
* Button to Export Data - CSV or XLSX
* Toggle for show/hide Hidden records
* Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first
* Should it have a text search?
* NOTE: It is probably easiest for them to us the search tab to find a lead that has already been added. It will show "View Lead" button if already added.
Functions needed:
* Load Leads function to get Exhibit_tracking entries for the Exhibit.
* Filter function to filter by Licensed user email address.
* Sort function to sort by selected option.
* Export function to export displayed Leads to CSV or XLSX.
- **Summary**: Investigation, targeted repair, and successful integration of the AE Firefly theme family and key UI components. This document records the final resolution, the migration of the Svelte 5 Modal component, and the validation of the `ae_app_3x_llm` branch as the project's stable baseline.
### 1. Root Cause Resolution (Themes)
- **Variables outside selectors**: Fixed. Custom-property declarations in `src/ae-firefly*.css` were moved into proper `[data-theme='AE_Firefly*']` selector blocks.
- **Variable precedence**: Resolved by enforcing `--background: ... !important` within the theme-specific selectors to ensure they override global `:root` defaults.
- **Files Repaired**:
-`src/ae-firefly.css`
-`src/ae-firefly-steelblue.css`
-`src/ae-firefly-indigo.css`
-`src/ae-firefly-rainbow.css`
### 2. Actions Taken (Recovery Phase)
- **Surgical Integration**: Selective harvesting of "90% done" work from WIP branches while preserving the integrity of the Lucide migration and Svelte 5 baseline.
- **`element_modal_v1.svelte`**:
- Successfully imported from `wip-modal-fix-attempt`.
- **Full Refactor**: Migrated from deprecated `svelte/legacy` and `<slot>` patterns to **Svelte 5 Snippets** and `{@render}` tags.
- Verified and tested as the new standard modal component.
- **Selective Vetting**:
- **Abandoned**: `element_input_v2.svelte` and legacy Badge View v1 (rejected due to legacy FontAwesome regressions and high error counts).
- **Removed**: Redundant `element_data_store_v2.svelte` (superseded by `v3`).
- **Kept**: Clean, Lucide-based versions of all core components already present on `ae_app_3x_llm`.
### 3. Repository State (Final Validation)
- **Baseline**: `ae_app_3x_llm` is now the unified, verified "known-good" state.
- **Validation**: `npx svelte-check` performed on the merged state returned **0 errors and 0 warnings**.
- **Cleanup**: Temporary integration branches have been deleted.
- **Backups**: `wip-modal-fix-attempt` and `wip/theme-fix` remain as reference points but are no longer active.
### 4. Merged Files (Key Updates)
-`src/ae-firefly*.css` (Repaired themes)
-`src/lib/elements/element_modal_v1.svelte` (New Svelte 5 Modal)
- **Verification**: Verified via `svelte-check` and theme inspections.
---
*Prepared by: Gemini CLI (March 17, 2026)*
---
*Archival note (2026-03-20): `element_modal_v1.svelte` (referenced in §2 as "new standard modal") was subsequently retired — it had zero active importers. Modal usage in the codebase relies on Flowbite `<Modal>` component. See `AE__UI_Component_Patterns.md` §11.*
2.**Missing microtask yields**: Added `await Promise.resolve()` after each `db_save_ae_obj_li__ae_obj()` call to ensure Dexie liveQuery observers fire before functions return.
3.**Fire-and-forget nested loads**: Changed `forEach()` to `await Promise.all()` in presentation loader to block until all presenter loads complete.
**Result:** Session view now renders presentations AND presenters on first load without manual refresh.
**Performance Impact:** Adds ~100-200ms to initial navigation (time to write 1-5 presenter records + microtask yields), but guarantees correct first-render.
-`documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md` - Added "try_cache: false" bug section with detailed explanation
- Code comments added explaining the critical fixes in all modified loader functions
- Journals module updated with microtask yields for consistency (already preserved try_cache correctly)
**Test Status:**
- Manual testing: ✅ Confirmed working (session + presentations + presenters render on first load)
- Playwright test: Created at `tests/coldstart_event_session.test.ts` (requires valid session ID to run)
**Lessons Learned:**
1.**Always preserve `try_cache` through nested data loads** - Disabling caching at any level breaks the entire chain
2.**Add microtask yields after IndexedDB writes** - Ensures liveQuery observers fire before functions return
3.**Block on nested loads with `await Promise.all()`** - Fire-and-forget forEach() causes race conditions
4.**Journals module was already correct** - It preserved `try_cache` for nested entry loads; only added yields for consistency
5.**The existing blocking pattern was sufficient** - No special helper function needed; the bug was in the loader implementation, not the architecture
**What We Implemented:**
We effectively implemented **Option A (Blocking Hydration)** but at a lower level than originally planned. Instead of creating a new `load_session_with_relations()` helper, we fixed the existing loader chain to properly block and cache nested data. The `+page.ts` already used `await` for the session load - it just wasn't working because the underlying loaders weren't caching nested data.
---
## Original Plan (For Reference)
Goal
Make the Presentation Management Session view deterministic on cold-start (empty IndexedDB). The page must render Presentations, Presenters, and Hosted Files without requiring manual refreshes.
- Minimize user-perceived latency — keep navigation snappy where possible.
- Avoid large architectural changes unless necessary.
Options (high level)
A) Blocking Hydration (recommended for correctness)
- Block the route `+page.ts` load until the session and all directly required related objects are fetched from the backend and written into IndexedDB. Return `initial_session_obj` in the load data for immediate rendering.
- Pros: simplest to guarantee first-draw correctness; minimal component changes.
- Cons: adds latency to navigation (can be mitigated with optimistic UI or progress indicator).
B) Prefetch Related Records + Hydrate Fallback (hybrid)
- Non-blocking load but `+page.ts` returns `initial_session_obj` and small related-objects payloads (presentations, presenter IDs, hosted_file metadata). Components use these fallbacks while `liveQuery` takes over.
- Pros: keeps navigation responsive; often sufficient.
- Cons: requires careful payload shaping and DB write ordering.
C) Explicit Dependency Chaining in UI (advanced)
- Keep non-blocking loads and use explicit dependency chaining: write session -> await write completion -> then write presentations -> await -> then presenters, ensuring microtask queue flushes between writes. Use targeted `liveQuery` re-creation only when upstream dependency fully resolved.
Start with Option A (Blocking Hydration) for the session page to restore deterministic behavior quickly. After correctness is achieved, consider converting to Option B or C for improved perceived performance if needed.
Detailed Steps (Option A - Blocking Hydration)
1) Add a small helper in `events_func` (e.g., `load_session_with_relations`) that:
- fetches session by ID from API
- fetches related presentations (limit/filters as needed)
- fetches presenters referenced by those presentations (deduplicate IDs)
- fetches hosted_file metadata for presentation files (if required for the view)
- writes all results to IndexedDB in a controlled order (session -> presentations -> presenters -> hosted_files)
- returns a compact `initial_session_obj` payload containing fields needed for first-draw (session, presentation list, presenter summary)
Implementation note: Use `await db.transaction('rw', db_events.session, db_events.presentation, db_events.presenter, async () => {...})` if atomicity helps. Alternatively write in sequential awaits and call `await Promise.resolve()` after each write to let the microtask queue settle.
2) Update route loader: `src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.ts` (create if missing) to call and `await` the helper, then return `initial_session_obj` on `data`.
3) Ensure the page component `+page.svelte` uses the `initial_session_obj` as immediate fallback (it already does in Aether).
4) Add instrumentation logs inside `liveQuery` closures and the helper to verify ordering during QA.
5) Add tests (see below) and manual verification steps.
Alternative (Option B - Hybrid) Implementation Notes
- If you cannot block the route, return an `initial_session_obj` that includes minimal related object arrays (IDs + small metadata) and have `+page.svelte` write those into IDB before mounting heavy child components.
- Use `untrack()` to set selection IDs so stores are updated without causing premature reactivity loops.
Explicit Dependency Chaining (Option C) Notes
- Implement a single `prefetch` function that sequentially performs writes and `await Promise.resolve()` between stages.
- For debugging, add microtask delays (e.g., `await 0`) between writes to observe behaviour.
Testing and Verification
1) Integration test (Playwright recommended)
- Clear IndexedDB for the app origin.
- Navigate to `/events/<event_id>/.../session/<session_id>` and assert that the presentation list and presenters are visible within N ms without manual refresh.
- Repeat on subsequent navigations to ensure no regressions.
2) Unit tests
- For `events_func.load_session_with_relations`, stub API responses and assert DB writes are made in expected order.
3) Manual QA
- With a cold profile or after clearing Site storage, navigate to the session page and confirm content is present after the initial navigation and that no manual refreshes are required.
Migration and Rollout
- Implement Option A behind a feature flag if you want to control rollout.
- Short-term: apply Option A to the single problematic route to reduce blast radius.
- Long-term: consider a library-level helper to standardize "blocking prefetch for nested related records" across other pages.
Rollback Plan
- Because changes are additive and limited to one route and helper, revert the `+page.ts` modification and helper call to restore prior behavior.
Deliverables for tomorrow
-`events_func.load_session_with_relations` helper (TS) + unit tests
- Updated `+page.ts` loader for session route to `await` helper and return `initial_session_obj`
- Small test harness / Playwright test that reproduces the cold-start issue and verifies the fix
- Hybrid or chaining implementations: additional 2-6 hours depending on thoroughness
Notes about Svelte 5 + Dexie specifics
- Keep `liveQuery` closures stable; capture primitive IDs rather than reactive objects.
- Use `$derived` and `$derived.by` to keep observable instances stable across renders.
- Use `untrack()` when setting selection values to avoid premature subscriptions.
- After DB writes, allowing the microtask queue to settle (`await Promise.resolve()`) helps ensure observers are notified in the expected order during development and debugging.
If you want I can implement Option A for the session route tomorrow (create helper, update loader, add test).
> `ae_app` is live in `aether_container_env/docker-compose.yml`. Both frontend and backend deploy together via `npm run deploy:staging` / `npm run deploy:prod`. Internal `ae_api` networking active. Healthcheck wired. Old `ae_env_node_app` is superseded (archive when ready).
> **Goal:** Consolidate the SvelteKit Frontend and FastAPI Backend into a single Docker Compose environment within `aether_container_env`.
## 1. Overview & Benefits
Currently, the platform runs in two separate Docker environments (`ae_env_node_app` and `aether_container_env`). Combining them into one allows for:
- **Unified Lifecycle:** Start/Stop/Update the entire stack with one command.
- **Internal Networking:** The Frontend (SvelteKit) can communicate with the Backend (FastAPI) via the high-speed internal Docker network (`ae_api:5005`) instead of routing through external IPs.
- **Shared Infrastructure:** Single instances of Redis, Dozzle, and Nginx serving both tiers.
- **Simplified Deployment:** Reduces the need for sibling directory dependencies on production servers.
---
## 2. Target Architecture (Workstation & Prod)
The unified environment will reside in `~/OSIT_dev/aether_container_env/`.
### Directory Mapping
- **API Source:** `~/OSIT_dev/aether_api_fastapi` -> Mounted to `ae_api`
- **App Source:** `~/OSIT_dev/aether_app_sveltekit` -> Mounted to `ae_app`
template['contact']['other_site_url']={'obj_type':'contact','obj_prop_name':'other_site_url','type':'url','placeholder':'Other site URL','class_li':[],'style':'','readonly':'','disabled':'','required':'','label':'Other site URL','label_class_li':[]};
template['contact']['others_site_name']={'obj_type':'contact','obj_prop_name':'others_site_name','type':'text','placeholder':'Other site name','class_li':[],'style':'','readonly':'','disabled':'','required':'','label':'Other site name','label_class_li':[]};
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.