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:
15
.ae_brief
15
.ae_brief
@@ -1,22 +1,15 @@
|
||||
# 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
|
||||
|
||||
## 🛠️ What I Just Did
|
||||
Addressed multiple Svelte compiler warnings:
|
||||
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.
|
||||
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.
|
||||
|
||||
## 🚧 Current Blockers
|
||||
None. Remaining svelte-check warnings (219) require more granular ID/for linking in complex forms.
|
||||
None.
|
||||
|
||||
## ➡️ Exact Next Steps
|
||||
1. Granular fix for remaining 68 label/ID associations in address/person forms.
|
||||
2. Systematic application of untrack() for remaining state-from-props warnings.
|
||||
3. Clean up unused TipTap CSS selectors identified by svelte-check.
|
||||
User to review changes. Ready for onsite testing/deployment.
|
||||
|
||||
---
|
||||
*Generated by ae_brief*
|
||||
|
||||
@@ -169,8 +169,8 @@ per event via config.
|
||||
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
|
||||
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
|
||||
5. **Presenter proceeds to room** — podium kiosk already has the file cached
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
- **Repo:** `~/OSIT_dev/aether_app_native_electron/`
|
||||
|
||||
@@ -72,10 +72,15 @@ Files are stored persistently using their SHA-256 hash to prevent filename colli
|
||||
- **Filename:** `{hash}.file`
|
||||
|
||||
### 4.2 Background Sync (File Warming)
|
||||
When a user navigates to a session in the Launcher UI, the `LauncherBackgroundSync` component:
|
||||
1. Extracts all `event_file_id` values for that session.
|
||||
2. Checks the native cache via `aetherNative.check_cache`.
|
||||
3. Triggers background downloads for missing files via `aetherNative.download_to_cache`.
|
||||
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. **Metadata Fetch:** The system fetches all sessions, presentations, and presenters for the current location into the local database (Dexie).
|
||||
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)
|
||||
When a user clicks "Open", the system follows a non-destructive sequence:
|
||||
|
||||
@@ -119,6 +119,9 @@ export interface LauncherSessState {
|
||||
trigger_reload__event_session_obj_li: string | null;
|
||||
trigger_reload__event_location_obj_id: string | null;
|
||||
trigger_reload__event_location_obj_li: string | null;
|
||||
|
||||
trigger__force_location_sync: any;
|
||||
|
||||
trigger__ws_connect: 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_li: null,
|
||||
|
||||
trigger__force_location_sync: null,
|
||||
|
||||
trigger__ws_connect: null,
|
||||
trigger__ws_disconnect: null
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
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 { Pause, Play, RefreshCw } from '@lucide/svelte';
|
||||
import { CloudDownload, Pause, Play, RefreshCw } from '@lucide/svelte';
|
||||
interface Props {
|
||||
on_expand?: () => void;
|
||||
}
|
||||
@@ -41,6 +41,16 @@ let { on_expand }: Props = $props();
|
||||
</button>
|
||||
</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}
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
<!-- Technical Timers (Edit Mode Only) -->
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -377,7 +389,40 @@ async function run_sync_cycle() {
|
||||
.anyOf(all_for_ids)
|
||||
.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;
|
||||
let cached_count = 0;
|
||||
@@ -554,6 +599,45 @@ async function refresh_location_config() {
|
||||
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>
|
||||
|
||||
<!-- Monitor Overlay: only shown in Native/App mode.
|
||||
|
||||
Reference in New Issue
Block a user