Non-trusted users see joh***@example.com in the confirm() prompt and
button title instead of the full address. Trusted staff see the full
email unchanged. Matches the obscure_email() pattern already used in
ae_comp__badge_obj_li.svelte.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Anonymous users (potentially stuck on stale JS) now see a Full Reset
button in the panel header instead of Clear IDB — matching the same
compact icon+reveal-label pattern as the authenticated Clear IDB button,
but error-styled and wired to handle_clear_storage_and_idb.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Button now shows a live countdown (7→0s) so users can see exactly when
the auto-reload will fire, with a RefreshCw icon per button UX standard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Displays the actual origin (e.g. https://idaa.org) and adds explicit
copy stating this does not affect other sites, browser history,
passwords, or settings — so users don't panic thinking their entire
browser is being wiped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sys bar: fix wrong title on Full Reset button (was "Clear IDB only",
now accurately describes SW + Cache + IDB + localStorage + sessionStorage)
- sys bar: rename "Reload Page" → "Clear IDB & Reload" and wire it to
handle_clear_idb_only instead of a bare window.location.reload()
- testing/fix-sw: expand from SW+Cache only to full storage nuke —
adds IDB (via indexedDB.databases() with known-names fallback),
localStorage, sessionStorage; auto-reloads to / after 3s; shows
per-step status log with success/warn/error colour coding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After clients.claim(), the new SW sends SW_ACTIVATE_RELOAD to every open
window tab. +layout.svelte now listens for this alongside the existing
controllerchange listener. The had_controller guard prevents a spurious
reload on fresh page loads where no SW was controlling the tab.
WHY: controllerchange only fires in JS that already has the listener
(added 2026-06-22). Tabs stuck on builds from before that date (e.g.
October 2025 users) never registered it, so the SW update under them
was silent. The postMessage path reaches those tabs directly. Users who
are already on new JS get the reload from controllerchange; users on old
JS get it from the message event once they receive this new SW.
Note: October 2025 users still need a manual Full Reset on their first
visit after this deploy (old JS has no message listener). After that one
reset they are permanently self-healing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Newly imported POC persons may not have a passcode set yet. The previous
guard caused send_poc_email_link() to silently exit for these persons,
making the email button appear to do nothing. Only poc_person_primary_email
is required to send the email; passcode falls back to '' matching the
presenter email button pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds checkbox column with select-all header, a selection action bar showing
count and "Delete X selected" (one confirm for the whole batch, sequential
delete with progress tracking), and a "Quick delete" checkbox that suppresses
the per-file confirm dialog for single-file deletes. Selection clears on
each new search. Designed for rapid orphan cleanup after Check Orphans.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleted `delete_ae_obj_id_crud` from api.ts (last legacy export, no callers)
and purged ~200 lines of commented-out dead functions from ae_core_functions.ts
(`load_ae_obj_id__site_domain`, `update_ae_obj_id_crud`, `update_ae_obj_id_crud_v2`).
V3 CRUD migration is now 100% complete with no legacy remnants. TODO updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all forbidden hardcoded colors (red/green bg, text-red-*, text-green-*)
with Skeleton semantic tokens (error/success) so forms render correctly in dark mode.
Update agreement text wrapper from bg-white/dark:bg-gray-900 to bg-surface-50-950.
Document Flowbite Svelte Modal standard pattern (incl. note that classes.footer
padding override does not work in current version).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Standardize all 3 consent modals (presenter × 2, session POC): same
placement="top-center" size="lg", no custom height hacks, let Flowbite's
native <dialog> handle scrolling; consistent footer Close button with X icon
- Modal titles now include person name; removes the double-header (inner <h2>
in both form components was redundant with the modal title)
- Agreement text wrapper: bg-surface-100-900 → prose dark:prose-invert +
bg-white dark:bg-gray-900 for proper dark-mode contrast on CMS HTML content
- Section/header bar changes color green when agreed (red when not), with
check icon; "Change to not agreed?" button style aligned between both forms
- Remove pb-16 from pres_mgmt layout (leftover from defunct sticky action bar)
- Add @source for flowbite-svelte/dist so backdrop:bg-gray-900/50 is generated
(modal backdrop dim was never active before — Tailwind wasn't scanning it)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes for presenters who have records in multiple sessions:
1. presenter_agree_ok now uses lq__event_presenter_obj.agree (the
current page's record) instead of lq__auth__event_presenter_obj.agree
(the sign-in record). Previously, agreeing on Presenter B while signed
in via Presenter A's link had no effect on B's upload gate.
2. presenter_sign_in_url derived conditionally omits person_id for
email-only presenters — URL builder moved from inline template
to a $derived so the condition is readable.
Removes unused lq__auth__event_presenter_obj liveQuery from presenter page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Email-only presenters (no Person record) no longer have person_id in
their sign-in URLs — sign_in_out falls back to presenter_id and
expand_auth_for_person resolves identity via Dexie lookup.
Person-linked presenters still include person_id as before.
Removes the confusing case where person_id == event_presenter_id in
URLs for email-only presenters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, presenters without a Person record had their email address used
as the person_id fallback in Copy Link and emailed sign-in URLs. This exposed
the email in browser history, server logs, and to anyone the link was shared
with.
Replaced .email fallback with .event_presenter_id in all three URL-building
locations:
- Copy Link clipboard value (presenter detail page)
- Email sign-in button person_id (presenter detail page)
- Email sign-in button person_id (presenter list component)
The sign-in handler's presenter_id_hint mechanism looks up the email from
Dexie using the event_presenter_id already in the URL, so cross-session auth
still works without the email being in the URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- poc_sign_in_url derived: replace encodeURI() with per-param
encodeURIComponent() — same fix applied to presenter URLs. passcodes
may contain special characters; encodeURI() would leave them unencoded.
- session_sign_in(): guard the presentation_id and presenter_id auth__kv
writes so they only run when non-null. A pure POC link has neither param
in the URL, so writing auth__kv[null] was creating junk 'null' string
keys that never matched anything.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- expand_auth_for_person: added presenter_id_hint param to look up the
signing presenter's email from Dexie, enabling cross-session auth even
when person_id in the URL is a string ID (not an email address)
- presenter_is_authed: added auth__kv.presenter[email] check so a
presenter signed in on one session auto-unlocks matching records across
all sessions for the same event
- URL construction: replaced encodeURI() with per-param encodeURIComponent()
in email_sign_in__event_presenter, email_sign_in__event_session, and the
Copy Link button — encodeURI() silently passes '+' unencoded, causing
URLSearchParams.get() to decode it as a space and break '+' email aliases
- Sign-in gate: changed from `if (url_person_pass)` to presence of
url_person_id + presenter_id/session_id so sign-in works when passcode
is empty/null (common for presenter records without a passcode configured)
- Fixed param?: Type syntax in sign_in_out.svelte (presenter_id_hint) —
Vite's type-stripping leaves the ? marker producing invalid JS on HMR
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pres_mgmt route: 8 co-located component files had no prefix, inconsistent
with every other module in the codebase. Renamed to ae_comp__* convention:
event_page_menu, locations_page_menu, location_page_menu, location_view,
presenter_page_menu, presenter_view, session_page_menu, session_view.
Each had exactly one importer — import paths updated.
leads route: ae_tab__ prefix was unique to this module. The 3 active tab
files renamed to ae_comp__tab_* (ae_comp__tab_add, ae_comp__tab_start,
ae_comp__tab_manage). Three unused files (ae_tab__add_search_scan,
ae_tab__lead_list, ae_tab__list) had zero importers and were removed.
PascalCase import bindings (AE_Record_Controls, AE_AITools, AE_Object_Flags)
left unchanged — Svelte requires capitalized component names in templates;
this is a style-only concern and not worth touching without a broader
component naming convention decision.
svelte-check: 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move 10 theme CSS files from src/ root to src/styles/ — update app.css
@import paths from ./ae-*.css to ./styles/ae-*.css. Keeps src/ root tidy
without changing how the themes are bundled.
- Trash aeclci_v1.css — not imported anywhere, dead file.
- Move pwa_install.svelte.ts from lib/pwa/ (single-file dir) into lib/elements/
alongside element_pwa_install_prompt.svelte; update 2 imports.
- Move src/types/temporary-svelte-augments.d.ts into src/lib/types/ (canonical
type location); trash empty src/types/ dir.
- idaa/clear-caches/ route slug NOT renamed — URL is embedded in Novi portal
iframe config; requires coordinated portal update outside this repo.
svelte-check: 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five Svelte components removed from ae_core/ (data/logic lib dir) and moved
to elements/ where reusable UI components live:
ae_comp__hosted_files_download_button, ae_comp__hosted_files_upload,
ae_comp__hosted_files_clip_video, ae_comp__hosted_files_clip_video_li,
ae_comp__site_config_editor — all import paths updated.
ae_core__organization.ts deleted — zero importers, dead code.
ae_events/types/ae_badge_template_cfg.ts promoted to ae_events root and
renamed ae_events__badge_template_cfg.ts (follows module naming convention);
types/ subdir removed.
ae_events/badges/css/ badge layout CSS files moved to elements/styles/
(the dedicated styles dir); badges/ subdir removed.
svelte-check: 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three pairs of duplicate/split directories collapsed into their canonical homes:
- src/lib/api/ → src/lib/ae_api/: api.ts moved alongside the individual
api_*.ts files it aggregates; all 85 import lines updated across the codebase.
- src/lib/utils/ → src/lib/ae_utils/: ae_string_snippets.ts and utils.ts
moved; one import updated (ae_stores.ts). utils.ts had no importers.
- src/lib/ae_elements/ → src/lib/elements/: AE_AITools, AE_Object_Flags,
AE_Record_Controls moved and renamed to snake_case (ae_ai_tools.svelte,
ae_object_flags.svelte, ae_record_controls.svelte); 6 import paths updated.
Local binding names left unchanged for a separate Group 2 pass.
svelte-check: 0 errors, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accessing navigator.serviceWorker.controller before the 'serviceWorker' in navigator
guard would throw a TypeError on browsers without SW support. Moved the guard into
the had_controller expression so the property is never accessed on unsupported browsers.
Also adds the missing trailing newline to core__export.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esbuild strips ': Type' from 'param?: Type' but leaves 'param?' in the
output, which is invalid JavaScript and causes Rollup to fail during the
Vite SSR build. Changed all 5 occurrences across source files from
'param?: Type' to 'param: Type | undefined = undefined', preserving the
same optional semantics while producing valid JavaScript after stripping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The reload should only fire when an existing SW is replaced by a new one
(old → new), not when the SW activates for the first time on a fresh page
load (null → first). The spurious reload on fresh loads was caused by
checking unconditionally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Vite define injects __BUILD_TIME__ and __BUILD_VERSION__ at build time
so /health returns the exact timestamp and package version of the running
build — useful for verifying deploys without guessing what changed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix two broken V1/V2 api calls (get_ae_obj_id_crud, update_ae_obj_id_crud)
that no longer exist on the api object — replaced with V3 equivalents
- Comment out two dead/uncalled functions (load_ae_obj_id__site_domain,
update_ae_obj_id_crud) and remove them from the export; pending full removal
- Extract download_export__obj_type into core__export.ts following the
core__*.ts module convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All cache-clearing buttons and the IDAA clear-caches page previously
cleared IDB/localStorage but left service worker registrations and Cache
Storage intact. On the next reload the SW re-served the old JS bundle,
leaving users stuck on stale code despite appearing to reload. This
caused recurring stale-state reports from IDAA and other clients for
4+ months.
Two gaps closed:
1. Every clear path (root page buttons, sys bar, help tech, idaa/clear-caches)
now unregisters SW registrations and clears Cache Storage before touching
IDB and localStorage. Order: SW → Cache Storage → IDB → localStorage.
2. Added controllerchange listener in +layout.svelte effect 4. When the new
SW activates and calls clients.claim(), this listener reloads the page so
open tabs run the new JS bundle instead of keeping old code in memory
indefinitely. Without this, skipWaiting + clients.claim work correctly on
the SW side but the page side never picks up the update.
Also added thorough code comments and updated REFERENCE__Common_Agent_Mistakes
(#15) and BOOTSTRAP doc (#8) to document the full root cause so this cannot
be silently re-broken by a future agent or refactor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Menu: Files moved between People and Data Stores; color tertiary (matches Users/People).
Dashboard: Sites secondary→primary, Files secondary→tertiary, Data Stores + Lookups surface→secondary. All cards now match menu color groupings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dashboard: Users + People cards corrected to tertiary (matched menu);
Files card added between People and Data Stores using secondary (matched menu).
Files per-page options extended to 200/400/800/1000/1500/2000.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When orphan filter is active, total was summing all 50 loaded files
rather than the filtered subset shown in the table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DELETE /v3/action/event_file/{id} now handles full atomic cleanup (link
removal, physical file, hosted_file record) in one call — replaces the
multi-step Redis pre-warm workaround. orphan_scan endpoint replaces the
N+1 per-file /links fetch on the admin files page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Event file delete (Pres Mgmt):
- Re-implement cleanup using /links pre-fetch before delete_hosted_file calls.
The /links endpoint calls get_id_random() per link which populates Redis.
Without this, redis_lookup_id_random('event_file', id) raises 404 in the
delete handler → silent skip → physical file never removed.
Now mirrors the same pattern used by the /core/files/ admin page delete.
/core/files/ admin page:
- Add orphan check mode: "Check Orphans" button batch-fetches links for all
visible results in parallel (reusing links_map cache), then filters table
to show only files with zero links.
- Orphan files get a warning badge in the filename column.
- Results header toggles to show "N orphans of M" when filter is active.
- Unlink icon imported from lucide for orphan UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
event_file delete was broken since the module was first created (Oct 2024).
delete_ae_obj_id__event_file passed delete_hosted_file=true + rm_orphan=true to
the generic V3 CRUD endpoint, but delete_obj_template never handled those params —
it only did a raw sql_delete on the event_file row. The hosted_file_link record,
hosted_file DB record, and physical file on disk were never cleaned up.
Fix: call api.delete_hosted_file (the action endpoint) BEFORE deleting the
event_file record so the backend can still resolve link_to_id via Redis.
Pass link_to_type='event_file', link_to_id=event_file_id, rm_orphan=true,
method=delete. hosted_file_id is now an optional param on
delete_ae_obj_id__event_file; component passes event_file_obj.hosted_file_id.
Also fix hosted_file delete in /core/files/ admin page (same root cause):
load links first, then call delete_hosted_file for each link with correct
link_to_type/link_to_id_random and method=delete before removing the record.
Also: add clickable navigation links in the files admin link sub-row.
Direct types (event, journal, archive, post) resolve immediately; nested types
(event_session, event_location, event_presenter, journal_entry, etc.) fetch
their parent ID via the V3 CRUD endpoint and construct the correct route.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Search hosted files across all accounts (including disabled)
- Sortable columns, pagination, per-row delete, download, and SHA-256 copy
- Lazy-load file link records per row via /v3/action/hosted_file/{id}/links
- Fix delete to load links first, remove each via correct link_to_type/link_to_id_random,
then hard-delete with method=delete and rm_orphan=true
- Remove Linked To and Group columns (moved Group/ForType to filter bar only)
- SHA-256 column now visible at lg breakpoint (was xl)
- Added /core/files nav link to /core layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Passcodes are no longer compared locally against cached localStorage data.
Entry now POSTs to /v3/action/auth/authenticate_passcode; on success the
returned JWT (with per-role TTL) is stored in $ae_loc.jwt. Page-load
expiry check in +layout.ts resets access_type to anonymous when the JWT
has expired, targeting only auth_type='passcode' JWTs.
- Debounce (600 ms) auto-fires the check after typing stops; Enter key
fires immediately as a secondary trigger — preserving the original UX
- Inline spinner and error message added to the passcode input
- Silent fallback to local comparison on network error or unresolved
site_id (ghost), so IDAA staff and Electron/Launcher contexts are safe
- USE_API_PASSCODE_AUTH = true (active); local fallback retained while
production is observed; site_access_code_kv cleanup deferred
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Removed invalid two-way bindings to read-only Dexie observables in the test page
- Fixed 'state_referenced_locally' warning in Field Editor by refactoring draft_value initialization
- Cleaned up empty else block in session list to resolve Vite build warning
- Verified successful production build and clean 'npm run check' output
- Completed rewrite of `element_ae_obj_field_editor.svelte` to Svelte 5 + Tailwind v4
- Set `display_modal = true`, `modal_blocking = false`, and `modal_placement = 'center'` as new defaults
- Implemented trigger-relative modal positioning with automatic viewport boundary clamping to prevent off-screen rendering
- Migrated all 12 call sites across core and events modules (Session, Presenter, Location, Exhibit, etc.)
- Removed legacy datetime-to-local manual conversion logic from views as the component now handles it natively
- Retired Skeleton-based legacy component
- Updated testing page and documentation to reflect the new standardized primitive
- Refactored core layout and dashboard to follow AE Firefly guidelines
- Added proper theme-aware backgrounds and transitioned to Skeleton v4 tokens
- Grouped navigation and cards by functional color (Teal: Infra, Indigo: Identity, Amber: Config)
- Reordered items to: Accounts, Sites, Users, People, Data Stores, Lookups, Addresses, Contacts
- Fixed color inconsistency for Activity Logs in dashboard vs navigation
- Enabled non-blocking modal mode for inline field editors in Data Stores table
- New `display_modal` prop opens the edit panel as a native <dialog> anchored
near the pencil trigger instead of shifting inline content
- `modal_placement` (center|above|below|left|right, default center) positions
the dialog relative to the trigger via getBoundingClientRect + CSS transform
- `modal_blocking` (default true) toggles showModal() vs show(); non-modal
mode adds a document pointerdown listener to close on outside click
- `cancel_edit()` now warns "Discard unsaved changes?" when draft differs from
saved value (matches data store form behaviour); skips warning after a
successful save
- Dialog background uses theme CSS vars directly (--color-surface-50/900) via
:global CSS — Skeleton tonal presets are intentionally semi-transparent and
rendered behind table content without explicit position:fixed + z-index
- Extracted edit panel to {#snippet edit_panel()} — shared by inline and
dialog paths with no duplication
- data-stores table: all three inline field editors switched to display_modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Edit button now has SquarePen icon + text per button UX standard
- Style guidelines: extract button icon+text rule into its own section 8
(was buried in Accessibility); renumber Accessibility to section 9;
add code examples and context table for the rule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>