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.
- 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>
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>
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>
Temporary rollback — non-moderators rejoin anonymously until Prosody is
configured with allow_empty_token=false to enforce JWT moderator claims.
TODO comment left in place to track the follow-up.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Issue JWT to all verified Novi users, not just moderators; unauthenticated
URL access no longer sufficient to join an IDAA video conference
- Remove 'embedmeeting' from Jitsi toolbar via explicit toolbarButtons whitelist;
the embed dialog exposed the Jitsi host/room URL violating IDAA privacy rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend updated (2026-03-31) to return default_qry_str in event API responses.
Frontend now stores it via properties_to_save and searches it in both the local
Dexie fast-path filter and the secondary post-API client filter. Previously, the
server searched default_qry_str (e.g. day-of-week, recurring_text) while the
client only checked name/description/location_text -- causing local results to
drop valid matches on revalidation (e.g. searching 'Thursday').
Also adds TODO note to audit other event search pages for the same mismatch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: url_uuid was read once from window.location.search (const),
assuming UUID changes always cause a full iframe reload (Novi impersonation).
Manual URL edits within the same SvelteKit session keep the layout mounted,
leaving url_uuid stale — the TTL cache then hit for the OLD valid UUID,
granting access under the wrong identity without re-verifying.
Fix:
- url_uuid is now $derived from $page.url.searchParams, updated on every
SvelteKit navigation
- url_uuid is read outside untrack() in Effect 2 so UUID changes trigger
a fresh verification run
- verify_failed (boolean) replaced with verify_failed_for_uuid (string|null)
so the retry-loop latch is keyed to the specific failed UUID — a different
UUID in the URL is always a clean slate that gets verified fresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clipboard API is blocked by default in many browsers when running inside
an iframe (requires explicit permission grant). IDAA members shouldn't need
to navigate browser settings to get a meeting link.
Added a readonly textarea below the two action buttons — click it to
select all, then Ctrl+C/Cmd+C. Works in every browser without any
permissions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security fixes (3 layers):
1. layout: verify_novi_uuid now rejects Novi 200 responses with no member
data — prevents non-existent UUIDs from passing as verified members
2. layout: access gate now requires $idaa_loc.novi_verified in addition to
novi_uuid (stale UUID alone was insufficient)
3. video_conferences: onMount guard aborts Jitsi init if the layout-verified
UUID doesn't match the URL UUID (defense-in-depth)
Also fixes an infinite verification loop: when verification fails, writes to
$idaa_loc trigger storage events that cause $ae_loc to re-notify subscribers,
re-running Effect 2 indefinitely. Added verify_failed latch to stop retries —
the UUID is fixed for the page lifetime, retrying always produces the same result.
Feature: "Open Externally" button + modal (iframe mode only) lets IDAA members
escape the Novi iframe when scrolling/layout is broken. Options: copy link to
clipboard or open in new tab. Accessible to all users without edit-mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If the "Verifying identity..." spinner is still visible after 8 seconds,
show an escape-hatch button that clears ae_loc + ae_idaa_loc from
localStorage and reloads — forcing a fresh site config fetch which
re-populates novi_idaa_api_key so verification can actually run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$ae_loc.trusted_access is only ever upgraded, never downgraded — it sticks
across Novi impersonation even though a different UUID is in the URL. Instead,
check user_id directly against $idaa_loc.novi_admin_li / novi_trusted_li so
the moderator grant is tied to the specific UUID being used, not the inherited
session access level.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
<svelte:head> scripts load asynchronously with no lifecycle hook to await
completion, so onMount could call init_jitsi() before JitsiMeetExternalAPI
was defined. Replace with a dynamic script loader that is awaited between
fetch_novi_data() and init_jitsi(). Also uses the domain from URL params
rather than the hardcoded jitsi.dgrzone.com hostname.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rather than hardcoding the IDAA admins group UUID or making an extra
API call, re-use the access level already established by the IDAA layout.
If $ae_loc.trusted_access is set (verified against novi_trusted_li /
novi_admin_li), the user is a moderator immediately. Only regular
authenticated members fall through to the group membership check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Older Novi pages that haven't been updated to pass g_uuid still need
the moderator check to work. Use [g_uuid] when present, otherwise fall
back to novi_idaa_group_guid_li from site config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of checking membership across all groups in novi_idaa_group_guid_li
(site config), pass the single g_uuid from the URL param. Each Novi iframe
page supplies the group relevant to that specific meeting, so checking just
that one group is both more precise and avoids unnecessary Novi API calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On a 429 response, waits 10 seconds then retries once. If the retry also
returns 429, throws and denies access (Reload/Retry button covers that case).
verify_in_flight and novi_verifying stay true during the wait so the spinner
remains visible and no concurrent calls can sneak in.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UUID is set by Novi via iframe src at page load and never changes within a
session (impersonation = full iframe reload). Reading it once from
window.location.search eliminates reactive noise from SvelteKit client-side
navigation causing spurious re-verification runs.
Removed:
- verify_dep $derived.by (reactive UUID + site_cfg narrowing)
- dedupe snapshot + last_effect_* tracking variables
- verify_backoff_attempts and exponential backoff retry logic
- novi_rate_limited_until writes and UUID-change guards
- ~80 lines of complexity
Kept:
- site_cfg_json read outside untrack (effect still re-runs when API key loads async)
- verify_in_flight concurrency guard
- TTL cache (prevents duplicate calls on SWR site_cfg updates)
- All permission upgrade and store write logic
NOTE: If Novi adds dynamic impersonation (no full reload), see comment at
url_uuid declaration for what to restore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical bugs fixed:
- $derived(() => {}) stored the function itself; uuid/api_key were always
undefined so verification never fired. Fixed to $derived.by(() => {}).
- novi_verifying pre-initialized to true (flash prevention) was also used as
the concurrency guard — guard saw it as in-flight and exited immediately,
leaving the spinner stuck forever. Split into separate verify_in_flight flag.
- $idaa_loc reads in dedupe snapshot (outside untrack) subscribed the effect
to idaa_loc writes, causing needless re-runs post-verification.
- Rate limit was not UUID-aware: 429 on one UUID blocked impersonation
(new UUID). TTL and rate-limit guards now both bypass when UUID changes.
Also includes: store defaults for novi_verified_ts + novi_rate_limited_until,
docs update, iframe template g_uuid param (prior agent changes).
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>
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>
- 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>
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>
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>
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>