- 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