Prevents silent no-op when user clicks submit before lq__exhibit_obj is ready
(exhibit not yet written to Dexie). Button now shows 'Loading...' spinner while
the exhibit record is resolving, eliminating the two-tap workaround needed on
first page load.
Also adds 7 Playwright tests for licensed user sign-in (leads_licensed_signin.test.ts)
covering success path, wrong credentials, email/identity tagging on captured leads,
identity isolation between staff members, and returning-session bypass.
Helpers: attach_leads_routes/setup_leads_test_page now accept exhibit_overrides
(e.g. license_li_json) to inject licensed users into mocked API responses.
seed_leads_loc import added to leads_auth.test.ts multi-exhibit test.
Total leads test coverage: 29 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /events/[event_id]/leads/config page: administrator UI for
mod_exhibits_json. Controls leads_require_payment toggle and Stripe
keys (publishable key + buy button IDs per license tier).
- leads_require_payment (mod_exhibits_json) now gates all billing UI:
header CreditCard button in exhibit +page.svelte and Licenses & Billing
accordion in ae_tab__manage.svelte. Default false (client covers costs).
- Stripe keys migrated from site_cfg_json to mod_exhibits_json (per-event).
ae_comp__exhibit_payment accepts them as optional props; falls back to
site_cfg_json for events not yet migrated.
- Fixed "My Leads" bug for shared-passcode users: search_params now maps
licensee_email 'my' → 'shared_passcode' literal (not kv.key passcode
string) so filters correctly match stored external_person_id values.
- Event settings: Exhibits section replaced with config link + raw JSON
fallback, matching pres_mgmt/badges pattern.
- Docs updated: README.md, MODULE__AE_Events_Exhibitor_Leads.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two corrections to the qry_files filter:
1. Switch from file_count to file_count_all — covers files on presentations
and presenters under the session, not just direct session files.
2. Switch "without files" from eq:0 to is_null — the view uses a LEFT JOIN
so sessions with no files get NULL, never 0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
qry_files was accepted as a parameter but never applied to the search query,
causing the "Sessions With/Without Files" report toggle to always return all
sessions regardless of the setting.
When qry_files !== null, automatically switch to the 'alt' view
(v_event_session_w_file_count) which exposes file_count, then add:
true → file_count > 0 (sessions with files)
false → file_count = 0 (sessions without files)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The field exists on the DB object but was missing from the TypeScript interface,
causing a false error in recovery_meetings search. Added it to db_events.ts where
it belongs. Removed the incorrect global DOM Event augment from the temp augments
file (was patching the wrong interface).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove show__launcher_link_legacy from PressMgmtRemoteCfg, PresMgmtLocState, and
pres_mgmt_loc_defaults — the Flask/legacy launcher is retired
- Sync function now hardcodes hide__launcher_link_legacy=true (always hidden)
- Config page: back button to pres_mgmt, save buttons disabled until changes made
- Fix {#each} key expressions in config page
- Migrate e_app_access_type and element_manage_event_file_li to pres_mgmt_loc store
- Add temporary svelte type augments file (src/types/)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces untyped $events_loc.pres_mgmt (svelte-persisted-store) with a
dedicated pres_mgmt_loc (runed PersistedState) backed by a fully typed
PresMgmtLocState interface and PressMgmtRemoteCfg for the server-side JSON.
Key changes:
- ae_events_stores__pres_mgmt_defaults.ts: canonical interfaces + defaults
covering all hide__/show__ fields, labels, report prefs, query filters,
and lock_config sync fields; qry_enabled uses 'not_enabled' (matches API)
- ae_events_stores__pres_mgmt.svelte.ts: new PersistedState store
- ae_events__event.ts: sync_config__event_pres_mgmt() rewired to write
directly to pres_mgmt_loc.current; launcher link inversion preserved
- All 26+ pres_mgmt templates migrated from $events_loc.pres_mgmt.* to
pres_mgmt_loc.current.*
- New config UI at (pres_mgmt)/pres_mgmt/config/ — manager + edit mode only
- Event settings page: removed embedded pres_mgmt form, links to config page
- event_page_menu: Config button visible only when manager_access + edit_mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
start_datetime and end_datetime were visible as chips but had no edit control.
Added two datetime-local field editors shown in edit_mode below the display chip:
- Converts stored "YYYY-MM-DD HH:mm:ss" → "YYYY-MM-DDTHH:MM" for the input
(safe because dayjs has no timezone plugin — times are stored as local time)
- Falls back to event start date + 08:00/09:00 when session datetime is null,
so staff only need to adjust the time rather than retype the full date
- Editors are side-by-side in a flex-wrap row with min-width so they wrap on mobile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The code badge was display-only — replaced with a field editor so staff
can correct session codes without going to a separate admin view.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Event location (FK lookup) and description were both visible in the session
view but had no edit controls — lost during V3 migration. Restored both:
- event_location_id: select dropdown populated from this event's location list
(liveQuery on db_events.location filtered by event_id from the session object)
- description: textarea editor shown directly in edit_mode (no collapse needed
when actively editing)
Also added event_location_id to editable_fields__event_session, which was
missing and would have caused backend rejections on PATCH.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Jitsi invite dialog can expose backend room URLs and paths.
Previously invite was gated on is_moderator (any Novi group moderator).
Now restricted to $ae_loc.trusted_access (IDAA staff in Aether) so
regular member moderators cannot send invites. All other toolbar
buttons are unchanged.
Previously only moderators received a JWT; non-moderators joined
anonymously. Now all verified Novi users get a JWT with the
is_moderator flag set appropriately, allowing the Jitsi server to
enforce authentication and respect context.user.moderator for
all participants.
Also adds JWT payload decode logging (client-side, signature not
verified) so the moderator flag and user identity can be confirmed
in the browser console during testing.
hide__session_code was defaulting to true, suppressing the code badge
in the session list on fresh sessions. Flip to false so codes are
visible out of the box — users can still hide via the menu toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `inc_file_counts` flag to `load_ae_obj_id__event_session` — maps to
backend alt view (v_event_session_w_file_count) when true; default stays
lightweight. Callers never pass raw view names.
- Preserve-on-write fallback in `_refresh_session_id_background` keeps
cached file_count/file_count_all if API response omits them.
- Session detail +page.ts uses `inc_file_counts: true` so SvelteKit prefetch
no longer clobbers counts via bulkPut on hover.
- Remove explicit `view: 'alt'` from launcher +page.ts (now invalid param).
- Session list link: flex-1 + min-w-0 for full-row width; name flex-1 pushes
badge group right; code + file_count stacked in flex-col items-end.
- Hover styling: button-like appearance with slow fade-out (duration-500) /
fast snap-in (hover:duration-150).
- Session +page.svelte: use url_session_id (string) for link_to_id props and
auth__kv.session[] index — fixes TS type error from number|undefined.
- IDAA layout: dormant tech notice banner (guarded by 1==3, remove when ready).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Access keys cleared from all site_domain records. Bypassing the entire
key verification block to unblock IDAA. TODO: restore when keys are re-added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When navigating within the iframe (e.g. meeting list → meeting detail),
the UUID is only present on the initial iframe src URL — internal SvelteKit
<a href> links don't carry it forward. The layout effect was unconditionally
clearing novi_verified on every navigation that lacked a UUID, causing
"Access Denied" on every internal link click.
Fix: if a valid TTL-cached Novi session exists when no UUID is in the URL,
treat it as internal navigation and preserve the session rather than wiping it.
Non-Novi paths (no session, no UUID) still clear and deny as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
key_checked was set to boolean true in Case 3, which +layout.svelte then
persisted back to localStorage. On the next keyless navigation, the check
true === 'actual-key-string' always failed, causing Access Denied after
just one internal page navigation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sites requiring a ?key= param (e.g. IDAA Novi iframe pages) no longer need
the key appended to every internal link after the first successful verification.
Stored key is always validated against the current site config from the API —
stale or rotated keys are denied immediately. Key present in URL always takes
the strict live-validation path with no cache shortcut.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unconditional assignment was overwriting $state defaults (incoming msg,
reactions, raise hand all muted) with false whenever the iframe template
didn't pass the sound URL params — which it never does.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>