From 65e48c764edbe9b8cf990185ebe234782e931f7d Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 28 May 2026 22:24:10 -0400 Subject: [PATCH] docs: document build_tmp_sort pattern and liveQuery filter dependency capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added two new sections to GUIDE__SvelteKit2_Svelte5_DexieJS.md: - IDB Sort: build_tmp_sort Pattern — sort chain, priority inversion encoding, anti-.reverse() warning, modules using it - $derived.by Dependency Capture — SCENARIO 2 filter pattern and API snapshot consistency fix Updated TODO__Agents.md: - Added anti-.reverse() warning and guide pointer to build_tmp_sort entry - Added Sessions hide/show toggle section with both fixes marked complete Co-Authored-By: Claude Sonnet 4.6 --- .../GUIDE__SvelteKit2_Svelte5_DexieJS.md | 82 +++++++++++++++++++ documentation/TODO__Agents.md | 18 ++++ 2 files changed, 100 insertions(+) diff --git a/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md b/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md index d6d4a3f8..936a063d 100644 --- a/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md +++ b/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md @@ -89,6 +89,88 @@ $effect(() => { - When you have chains (presentations depend on session; presenters depend on presentation.person_id), make the dependent liveQuery explicitly wait for the upstream ID and log inside each query to verify the order — adding a small `await Promise.resolve()` or `await 0` inside the `liveQuery` is sometimes useful during debugging to ensure the JS microtask queue has a chance to settle after DB writes. +## IDB Sort: `build_tmp_sort` Pattern (2026-05) + +All Aether objects support `priority`, `sort`, `group`, and `name` fields. Rather than sorting in JS after a Dexie query (which requires `.reverse()` hacks and duplicated logic), pre-compute up to three `tmp_sort_*` string fields during the processing pipeline and store them in Dexie. Then `.sortBy('tmp_sort_2')` does the right thing in one call, with no `.reverse()`. + +**Utility:** `src/lib/ae_core/core__idb_sort.ts` — `build_tmp_sort()` + +```typescript +import { build_tmp_sort } from '$lib/ae_core/core__idb_sort'; + +// Inside specific_processor callback: +const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({ + prefix: [obj.group ?? '0'], // always first + priority: obj.priority, // boolean; true→'0' so ASC sorts it first + sort: obj.sort, // zero-padded to 8 chars + fields_1: [...], // module-specific tier-1 fields + fields_2: [...], // tier-2 fields (tmp_sort_2 = base + tier-1 + tier-2) + fields_3: [...] // tier-3 fields +}); +obj.tmp_sort_1 = tmp_sort_1; +obj.tmp_sort_2 = tmp_sort_2; +obj.tmp_sort_3 = tmp_sort_3; +``` + +**Sort chain convention:** `group → priority DESC → sort ASC → [module-specific] → name` + +**Priority encoding:** `priority ? '0' : '1'` — inverted so that `priority=true` sorts first in ascending order. This means **never use `.reverse()`** on a list sorted by `tmp_sort_*` — `.reverse()` would flip priority-true to sort last. + +**Modules using `build_tmp_sort`:** +- `ae_events__event_presentation.ts` — `tmp_sort_1/2`: group → priority → sort → start_datetime → code → name +- `ae_journals__journal.ts` — `tmp_sort_1/2/3`: group → priority → sort → name → updated_on +- `ae_journals__journal_entry.ts` — same chain as journal + +Remaining modules (sessions, presenters, locations, posts, core) scheduled for rollout; see `TODO__Agents.md`. + +--- + +## `$derived.by` Dependency Capture for Extra Filter State + +When a `liveQuery` has a SCENARIO 2 fallback (broad search with no IDs), it may run before the debounced search fast path populates `event_session_id_li`. If that fallback doesn't apply the same visibility filter as the fast path, hidden items will briefly appear then disappear ("blink"). + +**Fix:** capture the filter flag as a `$derived.by` dependency in the outer closure so Svelte recreates the liveQuery instance whenever it changes — SCENARIO 2 then uses the correct filter from first render. + +```typescript +let lq__event_session_obj_li = $derived.by(() => { + const ids = event_session_id_li; // drives SCENARIO 1 vs 2 + const event_id = $events_slct?.event_id; + const qry_hidden = pres_mgmt_loc.current.qry_hidden; // extra dependency + + return liveQuery(async () => { + // SCENARIO 1 — specific IDs (fast path or API result) + if (Array.isArray(ids) && ids.length > 0) { + const results = await db.session.bulkGet(ids); + return results.filter(Boolean); + } + // SCENARIO 2 — broad fallback, uses captured qry_hidden + if (event_id && !someFilter) { + const all = await db.session.where('event_id').equals(event_id).sortBy('name'); + return all.filter((s: any) => { + if (qry_hidden === 'not_hidden') return !s.hide; + if (qry_hidden === 'hidden') return !!s.hide; + return true; // 'all' + }); + } + return []; + }); +}); +``` + +**Key rule:** anything read inside `$derived.by()`'s outer closure (but outside the `liveQuery` callback) becomes a Svelte reactive dependency. Changes to it recreate the liveQuery. Use this to synchronize filter flags that Dexie doesn't track. + +**Also fix the API call:** use the snapshot value from `params` (captured at debounce time) rather than the live store, so rapid toggling doesn't create a mismatch between fast path and API results: + +```typescript +// Bad — uses live store value, can race if user toggles during pending call: +hidden: pres_mgmt_loc.current.qry_hidden ?? 'not_hidden' + +// Good — uses snapshot captured when handle_search_refresh was called: +hidden: params.qry_hidden ?? 'not_hidden' +``` + +--- + ## Practical Patterns from Aether (Journals & Events & IDAA Recovery Meetings) - Journals: The journaling pages use SWR-style background refreshes but reliably render because either (a) the page `+page.ts` blocks to populate DB for critical views, or (b) components accept `data.initial_*` fallback values until `liveQuery` emits. This hybrid approach avoids the "refresh twice" problem while keeping navigation snappy. - Journals broad views: if text search is empty, let the local IDB result set drive the visible list. The API can revalidate the cache in the background, but it should not replace a broad "All" view with a limited slice that hides valid rows. diff --git a/documentation/TODO__Agents.md b/documentation/TODO__Agents.md index ff5b300c..69ffbe8c 100644 --- a/documentation/TODO__Agents.md +++ b/documentation/TODO__Agents.md @@ -57,6 +57,8 @@ The app uses `svelte-persisted-store` (coarse reactivity). Migration target: rep Shared utility in `src/lib/ae_core/core__idb_sort.ts` — fixes priority direction (inverted, true→'0' sorts first ASC) and zero-pads sort field (8 chars). No `.reverse()` needed. Sort chain: `group → priority DESC → sort ASC → [module-specific fields] → name`. +**⚠️ Never use `.reverse()` on a `tmp_sort_*`-sorted list — inverted priority makes it wrong.** +Documented in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` (IDB Sort section). - [x] `ae_events__event_presentation` — group + priority + sort + start_datetime + code + name - [x] `ae_journals__journal` + `ae_journals__journal_entry` — group + priority + sort + name + updated_on @@ -82,6 +84,22 @@ Sort chain: `group → priority DESC → sort ASC → [module-specific fields] --- +### [Pres Mgmt] Sessions hide/show toggle +- [x] **[Pres Mgmt] Hidden sessions blink on initial load** — SCENARIO 2 fallback in + `pres_mgmt/+page.svelte` now captures `qry_hidden` as a `$derived.by` dependency and + applies the filter in the fallback path. No blink on page load. (2026-05-28) +- [x] **[Pres Mgmt] API call uses live store instead of snapshot** — changed + `pres_mgmt_loc.current.qry_hidden` → `params.qry_hidden` in `handle_search_refresh` + API call to be consistent with fast path snapshot. (2026-05-28) +- **Note:** `hide_event_launcher` is still active — used in `menu_session_list.svelte` + (Launcher) to CSS-hide sessions from the list. Button to toggle it is in + `session_page_menu.svelte`. Not used in Pres Mgmt (intentional — Pres Mgmt always shows all). +- **Note:** Non-trusted users always have `!item.hide` applied at the component level + in `ae_comp__event_session_obj_li.svelte` regardless of `qry_hidden`. Toggle is + trusted-access-only in practice; direct session links still work for non-trusted users. + +--- + ## 🧪 Testing & Optimization - [ ] **[IDAA] IDB fast-path contact search** — parse `contact_li_json` in `search__event()`.