From 4cecc7a86022c2b4584d3519d353afc20c998e38 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 6 Mar 2026 20:25:31 -0500 Subject: [PATCH] style(launcher): accessibility, session list UX, and preset-* token fixes Style token fixes: - launcher_cfg.svelte: tab buttons preset-filled-primary-500 -> preset-filled-primary; opacity-50 inactive -> preset-tonal-surface - launcher_cfg_app_modes.svelte: same fix for app mode buttons (opacity-40) - launcher_cfg_controller.svelte: variant-filled-success/error -> preset-filled-* - launcher_cfg_template.svelte: variant-filled-success -> preset-filled-success - launcher_session_view.svelte: add dark:border-gray-600/700 to bare border-gray-* Session list (menu_session_list.svelte) -- full accessibility + UX pass: - fix: background sync fetches hidden:all so All Sessions toggle works - fix: hide_event_launcher respected in class:hidden and class:opacity-40 - fix: overlay uses explicit opaque backgrounds (slate-100/slate-800) to prevent preset-tonal-secondary transparency bleed-through in light and dark mode - feat: compact fixed 2rem row height; position:absolute overlay on hover/focus reveals full session name (300-char) without any layout shift (no sibling movement) - feat: active session always fully visible in flow (height:auto, no clipping) - a11y: hover_timer_wait 750->1200ms (motor accessibility) - a11y: removed hover:scale which caused cursor drift and timer jitter - a11y: px-1.5 py-1 touch targets, focus-visible ring for keyboard/switch users - a11y: fa-eye-slash icons distinguish hide vs hide_event_launcher states - docs: comprehensive OSIT/Aether-specific comments throughout --- .../launcher_cfg_app_modes.svelte | 12 +- .../launcher_cfg_controller.svelte | 4 +- .../launcher_cfg_template.svelte | 2 +- .../launcher_background_sync.svelte | 1 + .../[event_id]/(launcher)/launcher_cfg.svelte | 12 +- .../(launcher)/launcher_session_view.svelte | 4 +- .../(launcher)/menu_session_list.svelte | 231 ++++++++++++++++-- 7 files changed, 226 insertions(+), 40 deletions(-) diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte index 3ddbea66..3b93cb68 100644 --- a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte @@ -28,27 +28,27 @@ type="button" onclick={() => ($events_loc.launcher.app_mode = 'default')} class="btn btn-xs text-[9px] font-bold" - class:preset-filled-primary-500={$events_loc.launcher + class:preset-filled-primary={$events_loc.launcher .app_mode === 'default'} - class:opacity-40={$events_loc.launcher.app_mode !== + class:preset-tonal-surface={$events_loc.launcher.app_mode !== 'default'}>Web diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte index 95b7bace..008f7779 100644 --- a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte @@ -36,11 +36,11 @@ {#if ws_connected} Connected {:else} - Disconnected {/if} diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_template.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_template.svelte index 06f53eb2..ac262413 100644 --- a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_template.svelte +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_template.svelte @@ -117,7 +117,7 @@ >
Engine Health - Stable
diff --git a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte index ff6a86a3..01d2bae9 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte @@ -148,6 +148,7 @@ for_obj_type: 'event_location', for_obj_id: location_id, view: 'alt', + hidden: 'all', // Launcher is operator-only; fetch all sessions so the "All Sessions" toggle works try_cache: true, log_lvl: 0 }); diff --git a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte index 033883ca..1c968163 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte @@ -89,8 +89,8 @@ type="button" onclick={() => (active_tab = 'system')} class="btn btn-sm text-[10px] uppercase font-bold transition-all" - class:preset-filled-primary-500={active_tab === 'system'} - class:opacity-50={active_tab !== 'system'} + class:preset-filled-primary={active_tab === 'system'} + class:preset-tonal-surface={active_tab !== 'system'} > System @@ -98,8 +98,8 @@ type="button" onclick={() => (active_tab = 'sync')} class="btn btn-sm text-[10px] uppercase font-bold transition-all" - class:preset-filled-primary-500={active_tab === 'sync'} - class:opacity-50={active_tab !== 'sync'} + class:preset-filled-primary={active_tab === 'sync'} + class:preset-tonal-surface={active_tab !== 'sync'} > Sync @@ -107,8 +107,8 @@ type="button" onclick={() => (active_tab = 'general')} class="btn btn-sm text-[10px] uppercase font-bold transition-all" - class:preset-filled-primary-500={active_tab === 'general'} - class:opacity-50={active_tab !== 'general'} + class:preset-filled-primary={active_tab === 'general'} + class:preset-tonal-surface={active_tab !== 'general'} > General diff --git a/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte b/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte index 804e2fb6..7d36bc79 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte @@ -164,7 +164,7 @@ {#if $lq__event_session_obj && $lq__event_session_obj.event_session_id}

{#each $lq__event_presentation_obj_li as event_presentation_obj (event_presentation_obj.event_presentation_id)}
  • + /** + * menu_session_list.svelte — Aether Launcher: Session Selector + * + * PURPOSE: + * This is the primary navigation control for conference operators using + * the Aether Events Launcher. It lists all sessions in the selected room + * (event_location) and lets the operator switch the room's active session. + * + * ENVIRONMENT: + * The Launcher runs on operator laptops, dedicated podium/kiosk tablets, + * projector-connected desktops, and occasionally phones in breakout rooms. + * Users range from tech-savvy AV staff to volunteers with limited computer + * experience. Some users have motor impairments or shaky hands (e.g. older + * members common at IDAA and similar events). + * + * KEY DESIGN CONSTRAINTS: + * - Must show 0–20 sessions without scrolling (compact fixed-height rows) + * - Session names can be extremely long (~300 chars) — must truncate at + * rest but reveal fully on hover without pushing other rows around + * - Hover-to-switch fires after a delay timer (not instantly) to prevent + * accidental session changes from casual cursor movement + * - Strongly prefer click-to-confirm over hair-trigger hover activation + * - Works in light and dark mode; projector-safe high-contrast overlay + * + * DATA FLOW: + * lq__event_session_obj_li (Dexie liveQuery, passed from launcher/+layout.svelte) + * → rendered here as buttons + * → click / hover-timer sets trigger_reload__event_session_obj_id + * → $effect fires load + URL navigation + optional WS remote-control push + * + * SESSION VISIBILITY (operator toggle — show_content__hidden_sessions): + * Normal sessions: always visible + * hide_event_launcher = true: hidden from list by default (launcher-specific + * suppression, e.g. overflow/backup sessions) + * hide = true: globally hidden across all views (draft/cancelled) + * + * Both hidden states are fetched into Dexie with hidden:'all' by the background + * sync so the operator can reveal them via the "All Sessions" menu toggle. + * When revealed they appear dimmed (opacity-40) with an eye-slash indicator. + */ + interface Props { slct__event_session_id?: null | boolean | string; loading__session_id_status?: null | boolean | string; @@ -60,7 +101,14 @@ slct__event_presentation_li: null }); - let hover_timer_wait = 750; // Optimized from 1250ms for better responsiveness + // WHY 1200ms: Aether Launcher is used at conferences by operators of all ages and + // motor abilities — shaky hands, imprecise trackpads, and fat-finger tablet taps are + // routine. 750ms (the previous value) triggered accidental session changes when the + // cursor drifted across the list. 1200ms means the operator must deliberately hold + // focus on a row for over a second before it fires — still fast for intentional use. + // NOTE: hover-timer only triggers a data PRE-LOAD (preview). The session does not + // actually switch until the operator clicks. See onclick handler below. + let hover_timer_wait = 1200; let hover_timer: any = $state(null); // Navigation Shield Pattern (Refactored 2026-02-11) @@ -71,7 +119,7 @@ if (trigger_reload__event_session_obj_id) { const start = performance.now(); const event_session_id = String(trigger_reload__event_session_obj_id); - + if (log_lvl) { console.log(`[UI Trace] trigger_reload changed to: ${event_session_id}`); } @@ -179,22 +227,21 @@
      {#each $lq__event_session_obj_li as event_session_obj (event_session_obj.event_session_id)}
    • @@ -301,3 +364,125 @@
      No sessions found.
      {/if}
    + +