From d12a4bf71f5cf5fdb09d2f52a4009fe07151de7d Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Wed, 1 Apr 2026 16:38:13 -0400 Subject: [PATCH] feat(events): restore inc_file_counts opt-in, session list layout + button polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `inc_file_counts` flag to `load_ae_obj_id__event_session` — maps to backend alt view (v_event_session_w_file_count) when true; default stays lightweight. Callers never pass raw view names. - Preserve-on-write fallback in `_refresh_session_id_background` keeps cached file_count/file_count_all if API response omits them. - Session detail +page.ts uses `inc_file_counts: true` so SvelteKit prefetch no longer clobbers counts via bulkPut on hover. - Remove explicit `view: 'alt'` from launcher +page.ts (now invalid param). - Session list link: flex-1 + min-w-0 for full-row width; name flex-1 pushes badge group right; code + file_count stacked in flex-col items-end. - Hover styling: button-like appearance with slow fade-out (duration-500) / fast snap-in (hover:duration-150). - Session +page.svelte: use url_session_id (string) for link_to_id props and auth__kv.session[] index — fixes TS type error from number|undefined. - IDAA layout: dormant tech notice banner (guarded by 1==3, remove when ready). Co-Authored-By: Claude Sonnet 4.6 --- src/lib/ae_events/ae_events__event_session.ts | 28 ++++- .../ae_events_stores__pres_mgmt_defaults.ts | 2 + .../launcher/[event_location_id]/+page.ts | 1 - .../session/[session_id]/+page.svelte | 110 +++++++++++++++--- .../(pres_mgmt)/session/[session_id]/+page.ts | 1 + .../ae_comp__event_session_obj_li.svelte | 25 ++-- src/routes/idaa/(idaa)/+layout.svelte | 33 ++++++ 7 files changed, 170 insertions(+), 30 deletions(-) diff --git a/src/lib/ae_events/ae_events__event_session.ts b/src/lib/ae_events/ae_events__event_session.ts index 84fa9949..f97428bb 100644 --- a/src/lib/ae_events/ae_events__event_session.ts +++ b/src/lib/ae_events/ae_events__event_session.ts @@ -20,9 +20,9 @@ export async function load_ae_obj_id__event_session({ inc_all_file_li = false, inc_presentation_li = false, inc_presenter_li = false, + inc_file_counts = false, enabled = 'enabled', hidden = 'not_hidden', - view = 'default', limit = 100, offset = 0, try_cache = true, @@ -34,14 +34,18 @@ export async function load_ae_obj_id__event_session({ inc_all_file_li?: boolean; inc_presentation_li?: boolean; inc_presenter_li?: boolean; + // When true, uses v_event_session_w_file_count (backend 'alt' view) which includes + // file_count / file_count_all. Default false — the base view is cheaper and sufficient + // for most callers. Use true when the caller needs counts (e.g. session detail page load). + inc_file_counts?: boolean; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; - view?: string; limit?: number; offset?: number; try_cache?: boolean; log_lvl?: number; }): Promise { + const view = inc_file_counts ? 'alt' : 'default'; const start_time = performance.now(); if (log_lvl) { console.log( @@ -178,6 +182,26 @@ async function _refresh_session_id_background({ `📦 [Trace] _refresh_session_id: Received from API at ${elapsed}ms (id=${processed_obj.id})` ); + // PRESERVE AGGREGATE COUNTS: The individual session API view (view=default) + // does not compute file_count / file_count_all — those come from the list + // view SQL query. bulkPut replaces the full IDB record, so if we write + // undefined here we clobber the counts the list search already stored. + // WHY: SvelteKit link prefetching triggers this path on hover over session + // links in the search results list, causing the file count badge to blip. + // FIX: Read the cached counts and keep them if the API didn't return new ones. + if (try_cache && (processed_obj.file_count_all == null || processed_obj.file_count == null)) { + try { + const cached_id = processed_obj.id || processed_obj.event_session_id; + const cached = cached_id ? await db_events.session.get(cached_id) : null; + if (cached) { + if (processed_obj.file_count_all == null && cached.file_count_all != null) + processed_obj.file_count_all = cached.file_count_all; + if (processed_obj.file_count == null && cached.file_count != null) + processed_obj.file_count = cached.file_count; + } + } catch (_) { /* non-critical — best-effort count preservation */ } + } + if (try_cache) { await db_save_ae_obj_li__ae_obj({ db_instance: db_events, diff --git a/src/lib/stores/ae_events_stores__pres_mgmt_defaults.ts b/src/lib/stores/ae_events_stores__pres_mgmt_defaults.ts index c25d7e83..6bfd31b3 100644 --- a/src/lib/stores/ae_events_stores__pres_mgmt_defaults.ts +++ b/src/lib/stores/ae_events_stores__pres_mgmt_defaults.ts @@ -43,6 +43,7 @@ export interface PresMgmtLocState { show_content__session_search_room_name: boolean; show_content__session_view: string | null; show_content__session_qr: boolean; + hide__session_code: boolean; hide__session_msg: boolean; hide__session_poc: boolean; hide__session_poc_biography: boolean; @@ -159,6 +160,7 @@ export const pres_mgmt_loc_defaults: PresMgmtLocState = { show_content__session_search_room_name: false, show_content__session_view: null, show_content__session_qr: false, + hide__session_code: true, // Default hidden; toggle in ae_comp__events_menu_opts to show hide__session_msg: true, hide__session_poc: true, hide__session_poc_biography: true, diff --git a/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.ts b/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.ts index 5829bf52..a2550260 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.ts +++ b/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.ts @@ -80,7 +80,6 @@ export async function load({ params, parent, url }) { inc_all_file_li: true, inc_presentation_li: true, inc_presenter_li: true, - view: 'alt', try_cache: true, log_lvl: 0 }); diff --git a/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.svelte b/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.svelte index 96aea852..ebe11baf 100644 --- a/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.svelte +++ b/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.svelte @@ -25,7 +25,9 @@ import Session_view from './session_view.svelte'; import Session_page_menu from './session_page_menu.svelte'; import Comp_event_presentation_obj_li from '../../../../ae_comp__event_presentation_obj_li.svelte'; import Comp_event_presenter_form_agree from '../../presenter/[presenter_id]/ae_comp__event_presenter_form_agree.svelte'; -import { LoaderCircle } from '@lucide/svelte'; +import Comp_event_files_upload from '../../../../ae_comp__event_files_upload.svelte'; +import Element_manage_event_file_li_wrap from '$lib/elements/element_manage_event_file_li_direct.svelte'; +import { Archive, FileText, Info, LoaderCircle, Upload } from '@lucide/svelte'; // STABILITY FIX: Capture URL params reactively via $derived so liveQuery // closures see a stable identifier that updates on same-route navigation. let url_session_id = $derived(data.params.session_id); @@ -114,31 +116,101 @@ if (!$events_sess.pres_mgmt) $events_sess.pres_mgmt = {}; {lq__event_session_obj} {lq__auth__event_presenter_obj} /> - -
- -
+ + {#if $ae_loc.authenticated_access} +
+ +
+ {/if} - -
- +
+ {#if $ae_loc.trusted_access || $events_loc.auth__kv.session[url_session_id]} + + {#snippet label()} + +
+ + Upload session files +
+
+ Session-level handouts, agendas, or supplemental materials +
+
+ {/snippet} +
+ {/if} + +
+ +
+
+ {:else} + +
+ +
+ + +
+ - -
+ +
+ {/if} {#if !$lq__event_session_obj} diff --git a/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.ts b/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.ts index 464ae4eb..8657305a 100644 --- a/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.ts +++ b/src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.ts @@ -42,6 +42,7 @@ export async function load({ params, parent }) { inc_file_li: true, inc_presentation_li: true, inc_presenter_li: true, + inc_file_counts: true, // Use richer view so file_count badge is accurate on session detail page try_cache: true, log_lvl: log_lvl }); diff --git a/src/routes/events/ae_comp__event_session_obj_li.svelte b/src/routes/events/ae_comp__event_session_obj_li.svelte index 6dda1cc5..d25a7418 100644 --- a/src/routes/events/ae_comp__event_session_obj_li.svelte +++ b/src/routes/events/ae_comp__event_session_obj_li.svelte @@ -149,7 +149,7 @@ function toggle_details(id: string) {
+ class="hover:text-primary-800-200 hover:bg-surface-400-600 active:bg-surface-200-700 flex flex-1 flex-row items-center gap-2 rounded-lg px-2 py-2 text-left text-lg font-bold transition-colors duration-1000 hover:duration-300 min-w-0"> {#if session_obj?.hide} {/if} - {session_obj?.name} + {session_obj?.name} - {#if session_obj?.file_count_all} - - + {#if (!$events_loc.pres_mgmt.hide__session_code && session_obj?.code) || session_obj?.file_count_all} +
+ {#if !$events_loc.pres_mgmt.hide__session_code && session_obj?.code} + + {session_obj.code} + + {/if} - {session_obj.file_count_all} - + {#if session_obj?.file_count_all} + + + {session_obj.file_count_all} + + {/if} +
{/if}
diff --git a/src/routes/idaa/(idaa)/+layout.svelte b/src/routes/idaa/(idaa)/+layout.svelte index ebd5f864..4b71031d 100644 --- a/src/routes/idaa/(idaa)/+layout.svelte +++ b/src/routes/idaa/(idaa)/+layout.svelte @@ -64,6 +64,17 @@ let verify_failed_for_uuid: string | null = null; // Handles the case where site_cfg_json loads without novi_idaa_api_key (stale cache) // or the Novi API call hangs — the user would otherwise be stuck with no escape. const VERIFY_TIMEOUT_MS = 8000; + +// One-time technical notice banner — persisted in localStorage so it only shows once. +// TEMPORARY (2026-04-01): Remove this block after a few days. +const TECH_NOTICE_KEY = 'idaa_tech_notice_2026_04_01_dismissed'; +let show_tech_notice: boolean = $state( + browser ? localStorage.getItem(TECH_NOTICE_KEY) !== 'true' : false +); +function dismiss_tech_notice() { + show_tech_notice = false; + try { localStorage.setItem(TECH_NOTICE_KEY, 'true'); } catch { /* storage unavailable */ } +} let verifying_timed_out: boolean = $state(false); $effect(() => { @@ -353,6 +364,28 @@ async function verify_novi_uuid( {/if}
{:else if $ae_loc.trusted_access || ($ae_loc.authenticated_access && $idaa_loc.novi_uuid && $idaa_loc.novi_verified)} + {#if show_tech_notice && 1 == 3} + +
+ +

+ Opt 1: + Notice: We experienced technical difficulties last night (March 31) and this morning (April 1). Things are back to normal. We apologize for any inconvenience. + +
+
+ + Opt 2: + Notice: We have experienced some technical difficulties recently. Things are back to normal. We apologize for any inconvenience. +

+ +
+ {/if} {@render children?.()} {#if $idaa_loc.novi_uuid}