Commit Graph

2057 Commits

Author SHA1 Message Date
Scott Idem
9b0c05b80c fix(idaa): require JWT for all Novi users, remove embed meeting button
- 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>
2026-03-31 17:53:09 -04:00
Scott Idem
ae4b94f1b2 fix(idaa): expand recovery_meetings search to use default_qry_str from API
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>
2026-03-31 16:16:43 -04:00
Scott Idem
e6daf6b503 fix(bootstrap): validate access_key server-side, prevent stale cache bypass
When a URL access_key is present, skip the Dexie cache fast-path in
lookup_site_domain entirely — the key must be validated against the API.
Previously, a stale cached entry with a previously-valid key would be
returned immediately, allowing access even after the key changed or
was revoked in the URL.

Also: add site_domain_access_key to properties_to_save__site_domain
so domain-level keys are persisted to Dexie for cache validation;
remove shadow access_key re-declaration in +layout.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:07:41 -04:00
Scott Idem
84dc3dd158 feat(site): forward optional access_key from URL into site_domain search 2026-03-31 13:35:09 -04:00
Scott Idem
aa5ba8c9c6 docs: clarify access_key guidance; mark prod deploy completed in TODO__Agents.md 2026-03-31 12:57:43 -04:00
Scott Idem
c53a993bab Improved the open meeting externally buttons and style. 2026-03-30 20:06:23 -04:00
Scott Idem
d8ce04304b fix(idaa): re-verify UUID on SvelteKit navigation, not just full reloads
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>
2026-03-30 19:43:12 -04:00
Scott Idem
525ce1db79 feat(idaa): add manual-copy fallback textarea to breakout modal
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>
2026-03-30 19:16:16 -04:00
Scott Idem
6559e3393c fix(idaa): close Jitsi fake-UUID access hole + add breakout modal
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>
2026-03-30 19:15:21 -04:00
Scott Idem
702a7a73de docs: update architecture notes and TODO with Svelte 5 store migration plan
- AE__Architecture.md: minor wording fix
- TODO__Agents.md: add Svelte 4→5 store migration task (root cause of IDAA
  Novi re-auth bug; prerequisite for Phase 2c store refactor)
- PROJECT__Stores_Svelte5_Migration.md: new migration planning doc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 17:49:29 -04:00
Scott Idem
847d89054d feat(idaa): show reset button if Novi verification stalls after 8s
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>
2026-03-30 17:45:17 -04:00
Scott Idem
0d49ff3b8d fix(stores): bump AE_LOC_VERSION to 2; add ae_idaa_loc version wipe
AE_LOC_VERSION 1→2: force-clears stale ae_loc localStorage on next page
load for all users. Fixes users stuck on "Verifying identity..." in the
IDAA iframe — their cached site_cfg_json predated novi_idaa_api_key being
added to the site record, leaving api_key null so verification never ran.

AE_IDAA_LOC_VERSION 1: ae_idaa_loc (Novi auth state) was never included in
store_versions.ts — no wipe mechanism existed for it. Added now so future
schema changes can be handled cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 17:41:13 -04:00
Scott Idem
0e9a26cdca Another bug fix for IDAA and Novi verification. 2026-03-30 13:10:55 -04:00
Scott Idem
83e271a323 Version updates 2026-03-27 19:37:40 -04:00
Scott Idem
ace90ad043 docs(todo): document flowbite-svelte ModalProps errors and orphaned ShadCN packages
Records the root cause of the 2026-03-27 hidden-error discovery (broken ambient
declaration masking 31 pre-existing svelte-check errors), the lesson learned, and
two follow-up tasks: fix ModalProps.children across 26 files, remove shadcn-svelte
and bits-ui from package.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 19:35:44 -04:00
Scott Idem
d139ed1bd0 fix(types): add aria-hidden to IconProps augment; remove orphaned ShadCN components
- lucide-augment.d.ts: add `aria-hidden?: string | boolean` to IconProps
  (SVGAttributes drops this too in @lucide/svelte ≥ 0.577.0)
- Remove src/lib/components/ui/ — ShadCN primitives with zero importers;
  bits-ui API drift was generating ~20 type errors for dead code

svelte-check: 31 errors remaining (all ModalProps.children — flowbite-svelte
API change, deferred to next session), 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 19:32:24 -04:00
Scott Idem
3d988222a1 fix(types): move @lucide/svelte augmentation to module-context file
app.d.ts is a script-context declaration file. A `declare module 'x' {}`
in a script file is an ambient module declaration that completely replaces
the package's types — not an augmentation. This caused svelte-check to see
@lucide/svelte as exporting only IconProps, producing 1131 "class" errors
and 237 "no exported member" errors for every icon import.

Moving the augmentation to src/lucide-augment.d.ts with `export {}` makes
it a module file, so `declare module` becomes a proper augmentation that
merges with the package types. Result: Lucide errors drop from 1368 to 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 19:15:08 -04:00
Scott Idem
5433a906bb fix(types): restore class prop on Lucide IconProps after 0.577.0 breakage
@lucide/svelte >=0.577.0 dropped `class` from IconProps — it now derives
props purely from SVGAttributes<SVGSVGElement>, which TS types without
`class`. Every <SomeIcon class="..." /> in the codebase errored (1131
errors). Augment IconProps in app.d.ts to re-add `class?: string`.
Root cause: 0.561.0 → 0.577.0 bump in commit 366c6629 (2026-03-10).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 18:35:52 -04:00
Scott Idem
d89218be15 feat(leads): implement Stripe payment component for exhibit licenses
Full implementation of ae_comp__exhibit_payment.svelte (was a 9-line stub).
Reads Stripe config from $ae_loc.site_cfg_json per-event. License tier
selector (1/3/6/10 users) uses {#key} remount pattern to work around
stripe-buy-button web component ignoring attribute changes after mount.
Three states: paid confirmation (priority=true), not-configured hint, payment
form. client_reference_id=exhibit_id ties payments to booth records.
TypeScript declaration for stripe-buy-button added to app.d.ts via
svelte/elements augmentation. exhibit_id prop wired in +page.svelte and
ae_tab__manage.svelte.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 18:29:12 -04:00
Scott Idem
a8e9bd6694 Updated to do 2026-03-27 17:04:56 -04:00
Scott Idem
6cd3b5f8f9 More notes and comments updates 2026-03-27 16:21:51 -04:00
Scott Idem
b33c1b16f6 fix(idaa): check UUID against trusted/admin lists directly for Jitsi moderator
$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>
2026-03-27 15:17:38 -04:00
Scott Idem
d7a0857bed fix(idaa): load Jitsi external API script dynamically to eliminate race condition
<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>
2026-03-27 15:09:19 -04:00
Scott Idem
6939c058d8 Documentation updates 2026-03-27 14:53:28 -04:00
Scott Idem
b88a7de358 feat(idaa): trusted/admin users always get Jitsi moderator role
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>
2026-03-27 14:52:31 -04:00
Scott Idem
27f0bd21fb fix(idaa): fall back to site config group list when g_uuid not in URL
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>
2026-03-27 14:31:34 -04:00
Scott Idem
f111670f60 feat(idaa): use URL g_uuid for Jitsi moderator group check
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>
2026-03-27 14:27:06 -04:00
Scott Idem
045efa71e1 fix(layout): show sys bar in iframe when show_menu=true for trusted users
The {#if} gate only allowed the sys bar to mount for admins or
trusted+edit_mode users in an iframe. Trusted staff using show_menu=true
had sys_menu.hide set correctly but the component never mounted. Add
!sys_menu.hide as an escape hatch so the URL override actually works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 14:25:18 -04:00
Scott Idem
1e2c9d9b74 docs(idaa): document Novi API rate limits and backoff behavior
20 calls/sec, 600/min, 100k/day. Notes the 10s flat backoff + single retry
and the 5-min TTL cache that prevents normal-use rate limiting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 14:02:43 -04:00
Scott Idem
e64001cf63 fix(idaa): add 10s backoff retry on Novi API 429 rate-limit
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>
2026-03-27 13:59:50 -04:00
Scott Idem
4137d8677d refactor(idaa): simplify Novi verification — remove reactive UUID, dedupe, rate-limit
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>
2026-03-27 13:45:38 -04:00
Scott Idem
19d0145d00 fix(idaa): fix Novi UUID verification — stuck spinner, repeat calls, impersonation
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>
2026-03-27 13:38:42 -04:00
Scott Idem
9d44b9341c Now with the ability to actually create a badge. We still need to make this look nicer. Buttons should look more like button and consistent with the other areas of AE Events Badges. Also take a look at the trigger updated fields. 2026-03-27 11:51:42 -04:00
Scott Idem
bc67ff5798 docs(todo): mark Zebra driver install and test data setup complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 09:59:49 -04:00
Scott Idem
f87ab10251 feat(badges): add manual one-off badge create modal
Two-step creation: POST event_person first, then event_badge linked to it.
Badge create route (event_person parent) pending backend fix — frontend is
ready and passing event_person_id + event_badge_template_id in payload.

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 09:59:46 -04:00
Scott Idem
35c4341c34 docs(todo): add DevOps items — prod deploy, Bitbucket token migration, branch strategy, Gitea webhook
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 19:21:03 -04:00
Scott Idem
bd5759f037 docs(readme): update build/deploy section for new script names and env tiers
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>
2026-03-26 18:05:18 -04:00
Scott Idem
872e381c00 Added missing required var for API path 2026-03-26 17:54:00 -04:00
Scott Idem
64402e8e2a chore(scripts): rename deploy:* → build:docker:*, add deploy:remote:*
- 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>
2026-03-26 17:18:12 -04:00
Scott Idem
88b11b8318 Renaming files 2026-03-26 16:09:53 -04:00
Scott Idem
65e0477761 refactor(build): replace staging/cp env hack with vite --mode per-environment
- Rename .env.staging → .env.dev (and .default template)
- Add .env.test.default for the test tier (test-api.oneskyit.com)
- build:staging → build:dev/test/prod using vite --mode <name>
- deploy:staging → deploy:dev; add deploy:test
- Dockerfile: ARG BUILD_MODE=dev; explicit .env.runtime copy per mode
- .dockerignore: rewritten (deduped); allow .env.dev/.env.test/.env.prod
- .gitignore: track .env.dev.default and .env.test.default
- Remove dead PUBLIC_AE_* imports from ae_stores.ts (ACCOUNT_ID, EVENT_ID,
  NO_ACCOUNT_ID_TOKEN, SPONSORSHIP_CFG_ID); sponsorship_cfg_id defaults to null
- Strip dead vars from .env.prod.default template (AE_CFG_ID, AE_APP_NODE_PORT,
  ACCOUNT_ID, EVENT_ID, SPONSORSHIP_CFG_ID, NO_ACCOUNT_ID_TOKEN)
- GUIDE__Development.md: build:staging → build:dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 16:07:31 -04:00
Scott Idem
98736ae1bc chore(env): scrub real account IDs from .env.staging.default comments
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>
2026-03-26 14:08:18 -04:00
Scott Idem
7308a4773d docs(api): add V3 user actions section and clarify response shape
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>
2026-03-26 14:05:39 -04:00
Scott Idem
99541f0f9d fix(api): add explicit fetch CORS options and response header debug logging
Added mode, credentials, redirect, and cache options to the GET fetchOptions
object. These were previously left to browser defaults, which vary by environment
and can produce opaque CORS failures that are hard to diagnose. Being explicit
avoids environment-dependent surprises.

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

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

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

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

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 18:04:53 -04:00