From 4f8c482cf3da4564c43187af5bf6924cb68cbeab Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 27 Jan 2026 16:17:46 -0500 Subject: [PATCH] refactor(journals): stabilize reactive search and synchronize result counts - Refactored 'src/routes/journals/[journal_id]/+page.svelte' to use a singleton 'liveQuery' observable, eliminating the search subscription loop. - Synchronized entry counts in 'ae_comp__journal_obj_id_view.svelte' with the shared entries observable, ensuring accurate header feedback. - Cleaned up 'ae_comp__journal_entry_obj_li.svelte' by removing redundant script blocks and hardening visibility filters. - Restricted the result count badge to Edit Mode for a cleaner standard user interface. - Improved initial load UX with localized spinners and proper undefined-state guards. --- src/routes/journals/[journal_id]/+page.svelte | 99 ++++++++++++++----- .../ae_comp__journal_entry_obj_li.svelte | 57 ++++++++--- ..._comp__journal_entry_obj_li_wrapper.svelte | 42 ++------ .../ae_comp__journal_obj_id_view.svelte | 29 +++--- 4 files changed, 137 insertions(+), 90 deletions(-) diff --git a/src/routes/journals/[journal_id]/+page.svelte b/src/routes/journals/[journal_id]/+page.svelte index 19b79b98..ae3f388f 100644 --- a/src/routes/journals/[journal_id]/+page.svelte +++ b/src/routes/journals/[journal_id]/+page.svelte @@ -72,26 +72,63 @@ }) ); - // Standardized Reactive Search Pattern (Aether UI V3) - $effect(() => { - // 1. Reactive Dependencies - const qry_params = { - v: $journals_loc.entry.search_version, - str: $journals_loc.entry.qry__search_text, - cat: $journals_loc.entry.qry__category_code, - limit: $journals_loc.entry.qry__limit, - enabled: $journals_loc.entry.qry__enabled, - hidden: $journals_loc.entry.qry__hidden, - journal_id: $journals_slct.journal_id, - person_id: $ae_loc.person_id - }; + // Stable LiveQuery Pattern (Aether UI V3) + // Shared across ID view and List Wrapper + let lq__journal_entry_obj_li = $derived.by(() => { + // 1. Capture dependencies for Svelte tracking + const ids = search_id_li; + const journal_id = $lq__journal_obj?.journal_id; + const search_text = $journals_loc.entry.qry__search_text; + const cat_code = $journals_loc.entry.qry__category_code; + + // 2. Return the observable + return liveQuery(async () => { + // SCENARIO 1: Specific IDs provided (Search Results) + if (Array.isArray(ids) && ids.length > 0) { + if (log_lvl) console.log(`Journal Page LQ: bulkGet ${ids.length} IDs`); + const results = await db_journals.journal_entry.bulkGet(ids); + return results.filter(item => item !== undefined); + } + + // SCENARIO 2: Fallback to broad search (Default view) + // Only if search is empty and we have a journal context + if (journal_id && !search_text && !cat_code) { + if (log_lvl) console.log(`Journal Page LQ: Fallback search for journal: ${journal_id}`); + return await db_journals.journal_entry + .where('journal_id') + .equals(journal_id) + .reverse() + .sortBy('tmp_sort_1'); + } + + return []; + }); + }); + + // Standardized Reactive Search Pattern (Aether UI V3) + // 1. Isolate dependencies into a stable derived object + let search_params = $derived({ + v: $journals_loc.entry.search_version, + str: ($journals_loc.entry.qry__search_text ?? '').toLowerCase().trim(), + cat: $journals_loc.entry.qry__category_code, + limit: $journals_loc.entry.qry__limit, + enabled: $journals_loc.entry.qry__enabled, + hidden: $journals_loc.entry.qry__hidden, + journal_id: $journals_slct.journal_id, + person_id: $ae_loc.person_id, + remote_first: $journals_loc.entry.qry__remote_first + }); + + // 2. Controlled effect for triggering searches + $effect(() => { + // Track specifically the isolated search params + const params = search_params; - // 2. Debounce Logic if (search_debounce_timer) clearTimeout(search_debounce_timer); search_debounce_timer = setTimeout(() => { - // 3. Execution (Untracked to prevent loops) + // Execution MUST be untracked to prevent circular triggers untrack(() => { - handle_search_refresh(); + handle_search_refresh(params); }); }, 250); @@ -100,11 +137,10 @@ }; }); - async function handle_search_refresh() { + async function handle_search_refresh(params: any) { const current_search_id = ++last_search_id; - const journal_id = $journals_slct.journal_id; - const person_id = $ae_loc.person_id; - const remote_first = $journals_loc.entry.qry__remote_first; + const journal_id = params.journal_id; + const remote_first = params.remote_first; if (log_lvl) console.log(`[Journal Search #${current_search_id}] Refreshing entries (remote=${remote_first}, journal=${journal_id})...`); @@ -114,8 +150,8 @@ search_id_li = []; } - const qry_str = ($journals_loc.entry.qry__search_text ?? '').toLowerCase().trim(); - const cat_code = $journals_loc.entry.qry__category_code; + const qry_str = params.str; + const cat_code = params.cat; let local_ids: string[] = []; @@ -164,9 +200,9 @@ person_id: null, qry_str: qry_str || null, qry_category_code: cat_code || null, - enabled: $journals_loc.entry.qry__enabled, - hidden: $journals_loc.entry.qry__hidden, - limit: $journals_loc.entry.qry__limit, + enabled: params.enabled, + hidden: params.hidden, + limit: params.limit, log_lvl: 0 }); @@ -198,6 +234,8 @@ if (browser) { window.parent.postMessage({ journal_id: $journals_slct?.journal_id ?? null }, '*'); } + + import { LoaderCircle } from 'lucide-svelte'; @@ -206,16 +244,23 @@ -{#if $ae_loc.person_id == $lq__journal_obj?.person_id} +{#if $lq__journal_obj === undefined} +
+ +

Loading Journal...

+
+{:else if $ae_loc.person_id == $lq__journal_obj?.person_id} show_export_modal = true} on_show_import={() => show_import_modal = true} /> diff --git a/src/routes/journals/ae_comp__journal_entry_obj_li.svelte b/src/routes/journals/ae_comp__journal_entry_obj_li.svelte index 3db7616f..aff033ab 100644 --- a/src/routes/journals/ae_comp__journal_entry_obj_li.svelte +++ b/src/routes/journals/ae_comp__journal_entry_obj_li.svelte @@ -3,9 +3,15 @@ log_lvl?: number; lq__journal_obj: any; lq__journal_entry_obj_li: any; + show_found_header?: boolean; } - let { log_lvl = $bindable(0), lq__journal_obj, lq__journal_entry_obj_li }: Props = $props(); + let { + log_lvl = $bindable(0), + lq__journal_obj, + lq__journal_entry_obj_li, + show_found_header = true + }: Props = $props(); // *** Import Svelte specific import { goto } from '$app/navigation'; @@ -30,7 +36,9 @@ Siren, Tags, TypeOutline, - X + X, + LoaderCircle, + BookOpenText } from 'lucide-svelte'; // *** Import Aether specific variables and functions @@ -75,10 +83,13 @@ // Derived list of visible items (Standardized Search Pattern 2026-01-27) // Ensures count matches exactly what is rendered to the user let visible_journal_entry_obj_li = $derived((() => { + // Subscribe to the observable const list = $lq__journal_entry_obj_li; - - if (!list || !Array.isArray(list)) return []; - + + // Return null to signify 'loading' vs [] for 'empty' + if (list === undefined || list === null) return null; + if (!Array.isArray(list)) return []; + const filtered = list.filter((item: any) => { if (!item) return false; @@ -100,15 +111,23 @@
- {#if visible_journal_entry_obj_li && visible_journal_entry_obj_li.length} -
-

- Found: - - {visible_journal_entry_obj_li.length}× - -

+ {#if visible_journal_entry_obj_li === null} + +
+ +

Loading visible entries...

+ {:else if visible_journal_entry_obj_li.length > 0} + {#if show_found_header} +
+

+ Found: + + {visible_journal_entry_obj_li.length}× + +

+
+ {/if} {#each visible_journal_entry_obj_li as journals_journal_entry_obj, index}
@@ -527,8 +549,8 @@ )} Last update: {ae_util.iso_datetime_formatter( @@ -595,6 +617,9 @@ /> {/if} {:else} -

No Æ Journal Entry available to show. Please check the query filters or create a new Entry.

+
+ +

No Journal Entry available to show. Please check the query filters or create a new Entry.

+
{/if}
\ No newline at end of file diff --git a/src/routes/journals/ae_comp__journal_entry_obj_li_wrapper.svelte b/src/routes/journals/ae_comp__journal_entry_obj_li_wrapper.svelte index 8b169920..2fa7a267 100644 --- a/src/routes/journals/ae_comp__journal_entry_obj_li_wrapper.svelte +++ b/src/routes/journals/ae_comp__journal_entry_obj_li_wrapper.svelte @@ -1,65 +1,37 @@ {#if $lq__journal_entry_obj_li} {:else}
- +

Loading entries...

{/if} diff --git a/src/routes/journals/ae_comp__journal_obj_id_view.svelte b/src/routes/journals/ae_comp__journal_obj_id_view.svelte index b9036b47..1cc246f8 100644 --- a/src/routes/journals/ae_comp__journal_obj_id_view.svelte +++ b/src/routes/journals/ae_comp__journal_obj_id_view.svelte @@ -3,7 +3,7 @@ import { goto } from '$app/navigation'; // *** Import other supporting libraries - import { BookPlus, BookOpenText, FilePlus, Menu, Pencil, FileDown, FileUp, Settings } from 'lucide-svelte'; + import { BookPlus, BookOpenText, FilePlus, Menu, Pencil, FileDown, FileUp, Settings, LoaderCircle } from 'lucide-svelte'; // *** Import Aether specific variables and functions import { ae_util } from '$lib/ae_utils/ae_utils'; @@ -68,11 +68,11 @@ } return; } - + // Use journal.passcode_timeout (assuming it's in minutes, default to 5) const timeout_minutes = $lq__journal_obj?.passcode_timeout ?? 5; const timeout_ms = 1000 * 60 * timeout_minutes; - + if (log_lvl) { console.log(`Setting passcode timer for ${timeout_minutes} minutes (${timeout_ms}ms)`); } @@ -86,11 +86,11 @@ if (!$journals_sess?.journal_kv[$lq__journal_obj?.id]) { $journals_sess.journal_kv[$lq__journal_obj?.id] = {}; } - + // Reset verification and decryption flags $journals_sess.journal_kv[$lq__journal_obj?.id].journal_passcode_verified = false; $journals_sess.journal_kv[$lq__journal_obj?.id].journal_passcode_decrypted = false; - + passcode_timer = null; }, timeout_ms @@ -127,7 +127,7 @@ if ($journals_loc.entry.qry__category_code) { data_kv.category_code = $journals_loc.entry.qry__category_code; } - + try { const results = await journals_func.create_ae_obj__journal_entry({ api_cfg: $ae_api, @@ -135,7 +135,7 @@ data_kv: data_kv, log_lvl: log_lvl }); - + if (results?.journal_entry_id_random) { $journals_slct.journal_entry_id = results.journal_entry_id_random; $journals_loc.entry.edit_kv[$journals_slct.journal_entry_id] = 'current'; @@ -163,13 +163,18 @@

{@html $lq__journal_obj?.name ?? 'Loading...'} - {#if $ae_loc.trusted_access && $ae_loc.edit_mode} - ({$lq__journal_entry_obj_li?.length ?? '0'}×) + + {#if $ae_loc.edit_mode} + + {$lq__journal_entry_obj_li?.length ?? '0'}× + {/if} + {#await $journals_prom.load__journal_entry_obj_li} - - {:then} - + {/await}