From 6282fb167f69e27598ce45223b32dbb226a31c91 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 26 May 2026 19:55:17 -0400 Subject: [PATCH] fix(launcher): use $derived.by for session liveQueries to fix stale presentation/presenter data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When switching sessions within the same location, presentations and presenters were not updating. The root cause: plain $derived(liveQuery(...)) never recreates the Observable when slct__event_session_id changes, because liveQuery's async callback runs in Dexie's zone where Svelte tracking is off. Dexie's range-level change detection then ignores new session data (it arrives under a different event_session_id index value, outside the originally observed range). Replaced all four liveQuery declarations with $derived.by(() => { const id = ...; return liveQuery(...id...); }) — the same pattern already used in +layout.svelte for location-dependent queries. Svelte tracks the id read in the outer closure and recreates the Observable on every session change. Co-Authored-By: Claude Sonnet 4.6 --- .../(launcher)/launcher_session_view.svelte | 79 ++++++++++--------- 1 file changed, 42 insertions(+), 37 deletions(-) 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 23d3d6e2..a543c45c 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte @@ -50,10 +50,17 @@ import { Users } from '@lucide/svelte'; // Event Session (Main View Trigger) -// WHY: We use a simple derived observable. The template handles the $ prefix. -let lq__event_session_obj = $derived( - liveQuery(() => db_events.session.get(slct__event_session_id)) -); +// WHY: $derived.by captures the id in the outer closure so Svelte tracks it as a +// reactive dependency and recreates the Observable when slct__event_session_id changes. +// Plain $derived(liveQuery(...)) does NOT work here: liveQuery's async callback runs in +// Dexie's zone where Svelte tracking is off, so $derived never sees the id read and never +// recreates the Observable when the session changes. Dexie's range-level change tracking +// then keeps the stale Observable alive — it only re-fires when data in the originally +// observed key range changes, not when a different session's data arrives. +let lq__event_session_obj = $derived.by(() => { + const id = slct__event_session_id; + return liveQuery(() => db_events.session.get(id)); +}); // WHY: type_code drives poster vs. oral UI branching throughout this component. // It was previously a prop that was never passed by the parent, so all poster @@ -62,65 +69,63 @@ let lq__event_session_obj = $derived( let type_code = $derived($lq__event_session_obj?.type_code ?? ''); // Event File (for a Session) -// WHY: Pure data retrieval. Side effects (updating global stores) are removed -// to prevent circular reactivity loops during rapid navigation. -let lq__event_file_obj_li = $derived( - liveQuery(async () => { - if (!slct__event_session_id) return []; +// WHY: $derived.by — same reason as lq__event_session_obj above. Without recreating +// the Observable when slct__event_session_id changes, Dexie never re-fires for the new +// session's files (they land in a different for_id range than what was originally observed). +let lq__event_file_obj_li = $derived.by(() => { + const id = slct__event_session_id; + return liveQuery(async () => { + if (!id) return []; if (log_lvl > 1) { - console.log( - `[LQ] Fetching files for session: ${slct__event_session_id}` - ); + console.log(`[LQ] Fetching files for session: ${id}`); } return await db_events.file .where('for_id') - .equals(slct__event_session_id) + .equals(id) .reverse() .sortBy('created_on'); - }) -); + }); +}); // Event Presentation -let lq__event_presentation_obj_li = $derived( - liveQuery(async () => { - if (!slct__event_session_id) return []; +// WHY: $derived.by — same reason as above. Captures both id and sort_by in the outer +// closure so a new Observable is created whenever the session or its type changes. +let lq__event_presentation_obj_li = $derived.by(() => { + const id = slct__event_session_id; + const sort_by = type_code == 'poster' ? 'name' : 'start_datetime'; + return liveQuery(async () => { + if (!id) return []; if (log_lvl > 1) { - console.log( - `[LQ] Fetching presentations for session: ${slct__event_session_id}` - ); + console.log(`[LQ] Fetching presentations for session: ${id}`); } - let sort_by = 'start_datetime'; - if (type_code == 'poster') { - sort_by = 'name'; - } return await db_events.presentation .where('event_session_id') - .equals(slct__event_session_id) + .equals(id) .sortBy(sort_by); - }) -); + }); +}); // Event Presenter -let lq__event_presenter_obj_li = $derived( - liveQuery(async () => { - if (!slct__event_session_id) return []; +// WHY: $derived.by — same reason as above. +let lq__event_presenter_obj_li = $derived.by(() => { + const id = slct__event_session_id; + return liveQuery(async () => { + if (!id) return []; if (log_lvl > 1) { - console.log( - `[LQ] Fetching presenters for session: ${slct__event_session_id}` - ); + console.log(`[LQ] Fetching presenters for session: ${id}`); } return await db_events.presenter .where('event_session_id') - .equals(slct__event_session_id) + .equals(id) .sortBy('full_name'); - }) -); + }); +}); // let show_modal_upload_files: boolean = false; // let link_to_type: null|string = null;