feat(launcher): implement force-sync and chronological download priority

Onsite operators can now manually trigger a full pre-sync of all location
materials via a "Force Sync Location" button in the Launcher config.
This ensures podium Macs have all content cached before an event starts.

- Added trigger__force_location_sync to launcher session store.
- Implemented force_location_sync() in background sync engine to perform
  recursive metadata fetches for all sessions in a location.
- Optimized download queue with a 4-tier chronological sort:
  1. Event/Location assets (Global)
  2. Sessions by start time (Earliest first)
  3. Presentations by start time (Sequential order)
  4. File creation date (First-in fairness for on-time uploads)
- Updated module and native integration documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-21 22:29:13 -04:00
parent 60e3fc539e
commit 86201f0fc1
6 changed files with 129 additions and 20 deletions

View File

@@ -1,22 +1,15 @@
# Aether Project Brief: aether_app_sveltekit # Aether Project Brief: aether_app_sveltekit
**Last Updated:** 2026-02-09 22:03:56 **Last Updated:** 2026-05-21 22:25:05
**Current Agent:** mcp_agent **Current Agent:** mcp_agent
## 🛠️ What I Just Did ## 🛠️ What I Just Did
Addressed multiple Svelte compiler warnings: Implemented "Force Sync Location" feature. Optimized file download order with a 4-tier chronological sort (Global > Session > Presentation > Creation Date). Added UI button for onsite operators. Updated project documentation. Verified with npm run check.
1. Converted ~30 decorative labels to spans (a11y).
2. Applied Svelte 5 untrack() pattern to initial state from props.
3. Fixed CSS scoping for TipTap editor.
4. Added rel="noopener noreferrer" to external links.
5. Commited changes in two atomic batches.
## 🚧 Current Blockers ## 🚧 Current Blockers
None. Remaining svelte-check warnings (219) require more granular ID/for linking in complex forms. None.
## ➡️ Exact Next Steps ## ➡️ Exact Next Steps
1. Granular fix for remaining 68 label/ID associations in address/person forms. User to review changes. Ready for onsite testing/deployment.
2. Systematic application of untrack() for remaining state-from-props warnings.
3. Clean up unused TipTap CSS selectors identified by svelte-check.
--- ---
*Generated by ae_brief* *Generated by ae_brief*

View File

@@ -169,8 +169,8 @@ per event via config.
1. **Presenter checks in** — staff looks up their session(s) in Pres Mgmt 1. **Presenter checks in** — staff looks up their session(s) in Pres Mgmt
2. **File upload** — staff or presenter uploads file to the correct presenter/session record 2. **File upload** — staff or presenter uploads file to the correct presenter/session record
3. **File verification** — staff opens the file on a practice station to confirm it renders 3. **File verification** — staff opens the file on a practice station to confirm it renders
4. **Launcher sync** — file appears in the Launcher within the next polling cycle 4. **Launcher Sync** — file appears in the Launcher within the next polling cycle. Staff can use the **Force Sync Location** button in the Launcher config to immediately queue all files for the room's schedule.
5. **Presenter proceeds to room** — podium kiosk already has the file cached 5. **Presenter proceeds to room** — podium kiosk already has the file cached.
--- ---
@@ -280,6 +280,18 @@ For production onsite use, **Native mode on Mac laptops** is the target. The Ele
app pre-caches all session files in the background so presentations open instantly without app pre-caches all session files in the background so presentations open instantly without
a network round-trip at the moment of launch. a network round-trip at the moment of launch.
### Download Priority & Room Readiness
To ensure the podium is ready for the day's first sessions, the Launcher sync engine uses a
4-tier chronological sorting priority:
1. **Global Assets:** Event and Location level files (branding, walk-in slides) are cached first.
2. **Session Schedule:** Files for the earliest sessions in the room are prioritized.
3. **Presentation Order:** Within a session block, speakers are prioritized by their scheduled start time.
4. **First-In Fairness:** When times are equal, older uploads are prioritized over late revisions.
Operators can manually trigger a full pre-sync of all location materials via the **Force Sync Location** button in the Launcher configuration (Sync Engine section).
### Native Mode — Electron App ### Native Mode — Electron App
- **Repo:** `~/OSIT_dev/aether_app_native_electron/` - **Repo:** `~/OSIT_dev/aether_app_native_electron/`

View File

@@ -72,10 +72,15 @@ Files are stored persistently using their SHA-256 hash to prevent filename colli
- **Filename:** `{hash}.file` - **Filename:** `{hash}.file`
### 4.2 Background Sync (File Warming) ### 4.2 Background Sync (File Warming)
When a user navigates to a session in the Launcher UI, the `LauncherBackgroundSync` component: When a user navigates to a session in the Launcher UI, the `LauncherBackgroundSync` component warms the cache for that specific session. To ensure full room readiness, a **Force Sync Location** trigger is available in the configuration UI.
1. Extracts all `event_file_id` values for that session.
2. Checks the native cache via `aetherNative.check_cache`. 1. **Metadata Fetch:** The system fetches all sessions, presentations, and presenters for the current location into the local database (Dexie).
3. Triggers background downloads for missing files via `aetherNative.download_to_cache`. 2. **Chronological Priority:** Missing files are added to the download queue and sorted to prioritize the event schedule:
- **Tier 1: Global Assets** — Event and Location level files (virtual time 0).
- **Tier 2: Session Schedule** — Earliest sessions are prioritized first.
- **Tier 3: Presentation Order** — Within a session, speakers are prioritized by their start time.
- **Tier 4: Integrity & Fairness** — Tie-breakers use `created_on` (oldest first) to ensure on-time uploads are cached before last-minute revisions.
3. **Download:** Triggers background downloads via `aetherNative.download_to_cache` sequentially to preserve network bandwidth and ensure file integrity.
### 4.3 Safe Handover (Launch Sequence) ### 4.3 Safe Handover (Launch Sequence)
When a user clicks "Open", the system follows a non-destructive sequence: When a user clicks "Open", the system follows a non-destructive sequence:

View File

@@ -119,6 +119,9 @@ export interface LauncherSessState {
trigger_reload__event_session_obj_li: string | null; trigger_reload__event_session_obj_li: string | null;
trigger_reload__event_location_obj_id: string | null; trigger_reload__event_location_obj_id: string | null;
trigger_reload__event_location_obj_li: string | null; trigger_reload__event_location_obj_li: string | null;
trigger__force_location_sync: any;
trigger__ws_connect: any; trigger__ws_connect: any;
trigger__ws_disconnect: any; trigger__ws_disconnect: any;
} }
@@ -254,6 +257,8 @@ export const launcher_sess_defaults: LauncherSessState = {
trigger_reload__event_location_obj_id: null, trigger_reload__event_location_obj_id: null,
trigger_reload__event_location_obj_li: null, trigger_reload__event_location_obj_li: null,
trigger__force_location_sync: null,
trigger__ws_connect: null, trigger__ws_connect: null,
trigger__ws_disconnect: null trigger__ws_disconnect: null
}; };

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { ae_loc } from '$lib/stores/ae_stores'; import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores'; import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte'; import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { Pause, Play, RefreshCw } from '@lucide/svelte'; import { CloudDownload, Pause, Play, RefreshCw } from '@lucide/svelte';
interface Props { interface Props {
on_expand?: () => void; on_expand?: () => void;
} }
@@ -41,6 +41,16 @@ let { on_expand }: Props = $props();
</button> </button>
</div> </div>
<!-- Force Sync: fetches all child metadata for the location to trigger pre-caching -->
<button
type="button"
onclick={() => ($events_sess.launcher.trigger__force_location_sync = true)}
class="btn btn-sm preset-filled-primary-500 hover:preset-filled-primary-600 mb-3 w-full transition-all"
title="Fetch all sessions, presentations, and presenters for this room to force-sync all files to this machine.">
<CloudDownload size="1.2em" class="mr-2" />
Force Sync Location
</button>
{#if $events_loc.launcher.sync_intervals} {#if $events_loc.launcher.sync_intervals}
<div class="grid grid-cols-1 gap-3"> <div class="grid grid-cols-1 gap-3">
<!-- Technical Timers (Edit Mode Only) --> <!-- Technical Timers (Edit Mode Only) -->

View File

@@ -248,6 +248,18 @@ $effect(() => {
} }
}); });
// Force Sync Trigger: fetches ALL child metadata for ALL sessions in the room.
// WHY: by default, the launcher only warms presentation/presenter caches for the
// *selected* session to save bandwidth. Onsite operators use this trigger to
// force-fetch everything ahead of time so the file sync loop can pre-cache
// every slide deck for the entire day.
$effect(() => {
if ($events_sess.launcher.trigger__force_location_sync) {
$events_sess.launcher.trigger__force_location_sync = false;
force_location_sync();
}
});
/** /**
* API Refresh: Event * API Refresh: Event
*/ */
@@ -377,7 +389,40 @@ async function run_sync_cycle() {
.anyOf(all_for_ids) .anyOf(all_for_ids)
.toArray(); .toArray();
const cacheable_files = files.filter((file_obj) => !is_url_file(file_obj)); const cacheable_files = files.filter(
(file_obj) => !is_url_file(file_obj)
);
// Optimized Chronological Sync Order
// 1. Prioritize Event/Location level files (Global assets).
// 2. Prioritize by Session start time (Urgent schedule priority).
// 3. Prioritize by Presentation start time (Within a session).
// 4. Prioritize by created_on (Oldest first — "Not penalizing" on-time uploads).
cacheable_files.sort((a, b) => {
const time_a_sess = a.event_session_start_datetime
? new Date(a.event_session_start_datetime).getTime()
: 0;
const time_b_sess = b.event_session_start_datetime
? new Date(b.event_session_start_datetime).getTime()
: 0;
if (time_a_sess !== time_b_sess) return time_a_sess - time_b_sess;
const time_a_pres = a.event_presentation_start_datetime
? new Date(a.event_presentation_start_datetime).getTime()
: 0;
const time_b_pres = b.event_presentation_start_datetime
? new Date(b.event_presentation_start_datetime).getTime()
: 0;
if (time_a_pres !== time_b_pres) return time_a_pres - time_b_pres;
const created_a = a.created_on
? new Date(a.created_on).getTime()
: 0;
const created_b = b.created_on
? new Date(b.created_on).getTime()
: 0;
return created_a - created_b;
});
sync_stats.total = cacheable_files.length; sync_stats.total = cacheable_files.length;
let cached_count = 0; let cached_count = 0;
@@ -554,6 +599,45 @@ async function refresh_location_config() {
console.error('Sync: Location refresh FAILED:', err); console.error('Sync: Location refresh FAILED:', err);
} }
} }
/**
* Force Location Sync
* Fetches ALL sessions and their children (presentations, presenters, files)
* for the current location into Dexie.
*/
async function force_location_sync() {
const location_id = $events_slct.event_location_id;
if (!location_id) return;
if (log_lvl)
console.log(`Sync: FORCING full sync for location: ${location_id}`);
try {
// 1. Fetch all sessions with all children (recursive loads handled by load_ae_obj_li)
await events_func.load_ae_obj_li__event_session({
api_cfg: $ae_api,
for_obj_type: 'event_location',
for_obj_id: location_id,
inc_presentation_li: true,
inc_presenter_li: true,
inc_file_li: true, // Also get session-level files
view: 'alt',
hidden: 'all',
try_cache: true,
log_lvl: 1
});
if (log_lvl)
console.log(
'Sync: Force metadata fetch complete. File sync cycle will follow.'
);
// 2. Trigger the file sync cycle immediately to start downloads
run_sync_cycle();
} catch (err) {
console.error('Sync: Force location sync FAILED:', err);
}
}
</script> </script>
<!-- Monitor Overlay: only shown in Native/App mode. <!-- Monitor Overlay: only shown in Native/App mode.