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;