Allows sharing a clean link that auto-configures display state on any
device — no manual setup required, ideal for tablet PWA kiosk deployment.
All four params are optional and independent. Values persist in
localStorage so they survive reloads; the URL param always wins when
present. Read inside the existing reactive URL-sync $effect, which
fires on every SPA navigation (unlike the root onMount which only
fires once on initial load).
Supported params:
?iframe=true/false — hide/show global sys & debug menus
?launcher_menu=hide/show — hide/show left session/location panel
?launcher_header=hide/show — hide/show 'Æ Launcher v3' header bar
?launcher_footer=hide/show — hide/show the status footer
Example clean poster-kiosk link:
/events/{id}/launcher/{loc_id}?session_id={sess_id}
&iframe=true&launcher_menu=hide&launcher_header=hide
Create launcher_session_view_posters.svelte — a touch-first card-grid
layout for Digital Poster sessions, designed for tablet/phone PWA use.
Layout:
- 1 column on mobile, 2 on sm, 3 on xl
- Each poster card: title (line-clamp-3) + presenter name/affiliation +
'Open Poster' action button at card bottom
- Poster code (or 1-based index fallback) badge in top-right corner
- Card active:scale-[0.98] for tactile touch press feedback
- Sticky compact session header strip with name, code, and poster count
- Optional 'Session Resources' strip for rare session-level files
- overflow-y-auto + grow so the grid scrolls; header strip stays fixed
Integration:
- launcher_session_view.svelte: import + delegate when type_code==='poster'
- launcher_file_cont.svelte: min-w-96 → w-full on poster button so it
fills its container (card or list row) without overflow on small screens
- WS open/close/zoom command pipeline unchanged (all in launcher_file_cont
and +layout.svelte which were not modified for the WS paths)
The event_session_type_code field on file objects is often null, causing
poster-mode files to fall back to 'oral' and render as download buttons
instead of modal openers.
- launcher_session_view: session-level files now use derived type_code
- launcher_presenter_view: add session_type prop (default 'oral'); parent passes type_code
- launcher_presenter_view_posters: hardcode poster/modal (only ever rendered in poster context)
- launcher_menu event/location files unchanged: no session context, field is the only signal
- Add modal_zoom_fit state (default: fit); resets on every new poster open
- Zoom/Fit toggle button + image double-tap on controller tablet
- Both zoom triggers send ae_zoom:fit/zoom over WS to remote display (local_push)
- ae_zoom: handler added to handle_ws_recv() for remote device
- Replace 3 scattered close buttons with single bottom control bar:
- [Zoom/Fit] always visible on controller
- [Close Both] sends ae_close WS + clears local modal (local_push only)
- [Back to List] clears local modal only; remote keeps showing current poster
- Bottom bar hidden on controller=remote (kiosk display-screen mode)
- native pinch-to-zoom via touch-action: pinch-zoom on img (no JS library needed)
- pb-14 on modal bodyClass prevents buttons from overlapping poster content
- Generate and persist crypto.randomUUID() as controller_client_id on first
launcher load (events_loc is persisted so it survives page reloads).
Previously fell back to Date.now() on every reload.
- ae_open: handler now resolves presentation name via Dexie lookup chain:
file.for_id -> presentation.name, falling back to file.filename.
Remote modal now shows the presentation title instead of raw filename.
- launcher_file_cont: poster 'Open Poster' click now sends ae_open:event_file
command to remote display when controller=local_push and WS is connected
- +layout.svelte handle_ws_recv: ae_open: handler now looks up the file obj
from Dexie after setting modal__open_event_file_id, so the remote modal
has the hosted_file_id needed to render the poster image (was showing
'No image selected' on remote device)
- Update stale comment in menu_location_list.svelte: prop is already
$bindable(null), comment incorrectly said it was not
- Confirm cleanup_tmp_files is wired in launcher_background_sync.svelte
- Mark both items done in TODO__Agents.md
- launcher/+layout.svelte: dead {:else if $events_slct.event_session_id} branch
(condition identical to preceding {#if}) replaced with correct
{:else if $events_slct.event_location_id} — shows "Select a session"
prompt when a room is chosen but no session is yet selected.
- launcher/[event_location_id]/+page.svelte: removed unreachable
handle_get_device_info() function (never called; pre-relay pattern
superseded by launcher_background_sync.svelte's run_device_heartbeat()).
Cleaned up now-unused imports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- launcher_cfg_local_actions.svelte: add "Cache Maintenance" block
(native-only, gated by is_native && cache_root); number input for
max age in hours, "Clean Now" button with status feedback
- launcher_background_sync.svelte: read cleanup_tmp_max_age_hours
from events_loc.launcher store (default 24h) instead of hardcoded
1440 minutes; setting persists across sessions via persisted store
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- manifest.webmanifest/+server.ts: swap PUBLIC_AE_API_SECRET_KEY →
PUBLIC_AE_BOOTSTRAP_KEY (least privilege; endpoint only needs a
site-domain lookup, same as the bootstrap use case)
- electron_relay.ts: add cleanup_tmp_files() — runs `find ... -name
"*.tmp" -mmin +N -delete` via native run_cmd bridge
- launcher_background_sync.svelte: call cleanup_tmp_files() on mount
when is_native && cache_root are present (once per startup)
- AE__Permissions_and_Security.md: close Sev-1 audit language
- TODO__Agents.md: mark PUBLIC_AE_API_SECRET_KEY audit as complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New: menu_launcher_controls.svelte — bottom control bar for the launcher
sidebar with font size cycler (A / A+ / A−) and light/dark mode toggle,
always visible regardless of access level; visibility toggles (All Files /
All Sessions) moved here from launcher_menu.svelte and restyled to
preset-tonal-tertiary with descriptive title attributes
- launcher_menu.svelte — replace inline visibility-toggle block with
<Menu_launcher_controls />; add full header doc-comment describing
component structure and data flow
- menu_location_list.svelte — add header doc-comment covering purpose,
visibility rules, data flow, and the intentional non-bindable prop pattern
- documentation/TODO__Agents.md — mark font size cycler task complete
- 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>
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).
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.
- 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
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.
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
- Remove dead .field_editing_wrapper CSS rules from element_ae_crud_v2.svelte
(template migrated to field_editing_wrapper_v2 with Tailwind)
- Fix TS error: use optional chaining on person_obj key in ae_comp__person_obj_tbl.svelte
- Fix state_referenced_locally: wrap data.user init with untrack() in users/[user_id]/+page.svelte
- Replace misused <label> with <span> for visual section headings (a11y) in:
launcher_cfg_native_os.svelte, launcher_cfg_health.svelte,
launcher_cfg_local_actions.svelte, launcher_cfg_template.svelte
Fixed all 27 remaining instances across 19 files. Keys used:
- Object ID fields where available (e.g. account_id_random, event_file_id)
- index for logger lists with no reliable unique key
- Property name for Object.entries() loops
Miscellaneous small changes to events (badges, launcher, leads, pres_mgmt,
settings), journals, reusable elements (crud, field editor), app components,
core components, and test README. Mostly 1-2 line changes per file.
1. launcher_presentation_view: accept session_type prop from parent
instead of relying on event_session_type_code on file objects (which
is not reliably populated in Dexie). Use session_type to correctly
set hide_meta, session_type, and open_method on the file container.
2. launcher_session_view: pass session_type={type_code} to
Launcher_presentation_view; restore Launcher_presenter_view_posters
in the presenter list for poster mode (hide_name=true). Some events
store poster files at the PRESENTER level (for_id=event_presenter_id)
— particularly group/company presenters — so both paths must render.
3. launcher/+layout.svelte: fix poster modal image 403. The img src was
using the event_file download endpoint which requires auth headers a
plain img tag cannot send. Switched to the hosted_file endpoint with
key=account_id, which is browser-compatible. Also guarded on
modal__event_file_obj.hosted_file_id for safer access.
4. launcher_cfg_screen_saver: rename section title to 'Poster Screen Saver'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two issues in poster session view:
1. 'No files for this presenter' message always showed because
file_count on the presenter is 0 — poster files are attached to
the presentation, not the presenter. Removed the misleading message
from launcher_presenter_view_posters.svelte with an explanation.
2. launcher_presentation_view.svelte was not passing hide_meta, so
the OS toggle, date badge, and file size always rendered below the
poster button. Added hide_meta={true} for poster files to match the
clean display already used in launcher_presenter_view_posters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
native_device can be undefined during initial load (before device config
is fetched). The select bind:value was doing a hard property access that
threw a TypeError crash when native_device was null/undefined. Wrapped
in {#if $ae_loc.native_device} to defer rendering until it's available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The $effect in launcher_presentation_view.svelte was calling
load_ae_obj_id__event_presentation() on every prop update. The SWR
pattern in that function always fires a background Dexie write, which
triggered the upstream liveQuery, which updated the prop, which
re-ran the $effect — creating an infinite loop that saturated the API
and crashed the browser tab within 30-60 seconds.
Fix: guard on last_loaded_id so the API call only fires when the
presentation ID actually changes, not on every downstream re-render.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SWR pattern always fires a background API call on cache hits, so the
15s session interval created a continuous API stream even when data was
fresh. Increased all defaults: session 15s->60s, location/device 30s->60s,
presentation/presenter 45-60s->120s.
Added sync_paused guard to all six refresh functions and a Pause/Resume
toggle in the Sync Timers config section (visible without edit mode) so
testing and troubleshooting don't require a full reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
type_code was a prop in launcher_session_view that was never passed by
the parent layout, silently killing all poster-specific code paths
(sort order, section labels, poster presenter component, inline
presenter display). Fixed by deriving type_code directly from the
session LiveQuery object already present in the component.
Also adds a poster icon badge to the session list so poster sessions
are visually distinct from oral sessions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implemented explicit 'enabled' and 'hidden' parameter support in the Event data layer ('load_ae_obj_id__event').
Updated the Events Launcher layout and background sync engine to proactively fetch all enabled locations (including hidden ones), ensuring the room select list is complete and stays updated.
Refined 'launcher_file_cont.svelte' to only display native-specific file opening instructions when 'app_mode' is 'native'.
Updated AGENT_TODO.md to reflect task completion and new priorities.
- Updated 'handle_open_file' in launcher_file_cont.svelte to correctly pass filename to API.
- Fixed WebSocket ae_download command in launcher layout to include filename.
- Implemented a safety net in api_get_object.ts to extract filename from params if missing.
- Added 'download' attribute to Hosted Files download button for direct links.
- Refactored launcher menu components to use Svelte 5 global 'page' state instead of obsolete 'data_url' prop.
- Optimized session navigation by switching from static data_url to reactive $page.url.\n- Fixed selection hang when entering the Launcher without an initial session ID.\n- Synchronized global header and idle logic with derived layout observables.\n- Streamlined prop passing to ensure clean state transitions during session switching.
- Optimized background API refreshes to focus on the selected session instead of the entire room.\n- Eliminated redundant bulk database writes that were causing UI flickering during refresh cycles.\n- Staggered initial data fetches to prevent API request storms.\n- Added detailed logging for easier tracing of background sync operations.
- Standardized default view to 'alt' for session loading to ensure persistent file counts.\n- Hardened 'No files' warning logic to prevent false positives during background refreshes.\n- Restored reactive session observable in layout for global header and idle logic.\n- Documented overwrite pitfalls in SVELTE_DEXIE_GUIDE.md.
- Migrated background timers to persistent 'sync_intervals' store.\n- Updated Sync Monitor and Config UI to display and edit all six polling loops.\n- Ensured timer settings are applied and persisted regardless of native mode status.\n- Refined initialization logic to prioritize user configuration with robust fallbacks.
- Added background API refresh loops for room-level presentation and presenter metadata.\n- Updated Sync Monitor UI to display all six loop intervals.\n- Added UI controls in Launcher Config for fine-tuning polling periods.\n- Staggered initial background fetches to prevent API connection flooding.\n- Corrected timer assignments to separate structural metadata from room content.
- Added dedicated background timers for room-level presentation and presenter metadata.\n- Staggered initial data fetches to prevent request storms during room entry.\n- Cleaned up redundant timer assignments and separated API refresh from native file sync.\n- Optimized loop frequencies for better balance between freshness and performance.
- Silenced 'Heartbeat skipped' warnings when not running in native Electron mode.\n- Hardened device ID retrieval using String-Only ID prioritization logic.\n- Improved reliability of background sync cycles during session transitions.
- Moved session observable to child component to ensure reactive header updates.\n- Implemented deferred loading for presenters and files to prevent request storms.\n- Fixed WebSocket navigation paths to use store-backed IDs.\n- Streamlined Svelte 5 component lifecycle for better performance and SWR responsiveness.
- Optimized session list load to be shell-only, preventing initial request storms.\n- Moved deep data fetching (presenters/files) into the Presentation component level using Svelte effects.\n- Deferred child collection lookups until components are rendered in the DOM.\n- Fixed ae_api import in launcher_presentation_view.\n- Hardened background refresh logic to respect requested views.
- Initialized event_session_id to null in store template to prevent Svelte 5 binding errors.\n- Fixed invalid bind expressions in Launcher layout.\n- Corrected view: alt propagation in session list background refresh to ensure file counts are retrieved.\n- Hardened duplicate launch protection with verify_hash enabled by default.\n- Updated SVELTE_DEXIE_GUIDE.md with binding pitfall documentation.