Fix IDAA recovery meetings auto search
This commit is contained in:
@@ -149,6 +149,12 @@ subscribes to the **entire store**. This means unrelated writes to `$ae_loc`
|
|||||||
what you read from these stores inside `$effect` blocks. See `PROJECT__Stores_Svelte5_Migration.md`
|
what you read from these stores inside `$effect` blocks. See `PROJECT__Stores_Svelte5_Migration.md`
|
||||||
for the long-term fix plan.
|
for the long-term fix plan.
|
||||||
|
|
||||||
|
For search pages specifically, this usually means:
|
||||||
|
- keep true user preferences in persisted local state
|
||||||
|
- keep transient triggers, loading flags, and last-executed search keys in session state when possible
|
||||||
|
- let the page effect schedule the search, but put the duplicate-execution guard inside the search executor so page-load auto-search still runs after hydration
|
||||||
|
- if the search text or filters are mirrored from localStorage on mount, expect that mount-time writes can re-trigger the effect unless the executor has its own guard
|
||||||
|
|
||||||
### `{#await}` blocks
|
### `{#await}` blocks
|
||||||
```svelte
|
```svelte
|
||||||
{#await somePromise}
|
{#await somePromise}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ $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.
|
- 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.
|
||||||
|
|
||||||
## Practical Patterns from Aether (Journals & Events)
|
## 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: 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.
|
- 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.
|
||||||
|
|
||||||
@@ -98,6 +98,12 @@ $effect(() => {
|
|||||||
- Provide `initial_session_obj` from `+page.ts` as a first-draw fallback to child components.
|
- Provide `initial_session_obj` from `+page.ts` as a first-draw fallback to child components.
|
||||||
- Use `$derived.by(() => liveQuery(...))` for presentation lists so the observable instance is stable across renders and recreated only when `event_session_id` or `search` changes.
|
- Use `$derived.by(() => liveQuery(...))` for presentation lists so the observable instance is stable across renders and recreated only when `event_session_id` or `search` changes.
|
||||||
|
|
||||||
|
- Search pages with persisted filters or saved query text should keep the auto-search trigger in a page-level `$effect`, but the duplicate guard should live inside the actual search executor. That preserves the first page-load search while blocking repeated identical reruns from localStorage-backed rerenders. In practice:
|
||||||
|
- derive a single `qry_key` from the search inputs
|
||||||
|
- debounce in the `$effect`
|
||||||
|
- compare `qry_key` against a `last_executed_key` inside `handle_search_refresh()`
|
||||||
|
- keep transient loading flags and trigger counters in session state when the value is only used to force a refresh, not as a persisted preference
|
||||||
|
|
||||||
Example (presentation list pattern):
|
Example (presentation list pattern):
|
||||||
```typescript
|
```typescript
|
||||||
let lq__event_presentation_obj_li = $derived(
|
let lq__event_presentation_obj_li = $derived(
|
||||||
@@ -114,6 +120,7 @@ let lq__event_presentation_obj_li = $derived(
|
|||||||
- Add a small `console.log` inside each `liveQuery` closure to confirm when it runs and what `id` it sees.
|
- Add a small `console.log` inside each `liveQuery` closure to confirm when it runs and what `id` it sees.
|
||||||
- Verify that `+page.ts` either `await`s critical loads or returns `initial_*` payloads for first-render hydration.
|
- Verify that `+page.ts` either `await`s critical loads or returns `initial_*` payloads for first-render hydration.
|
||||||
- Confirm that dependent store values (selected IDs) are assigned before components subscribe — use `untrack` to prevent extra reactive cycles.
|
- Confirm that dependent store values (selected IDs) are assigned before components subscribe — use `untrack` to prevent extra reactive cycles.
|
||||||
|
- If a search page stops auto-loading after a localStorage change, check whether the duplicate guard was placed in the `$effect` instead of the executor. Guarding too early can suppress the initial search; guard at execution time instead.
|
||||||
- If a broad Dexie-backed list shows fewer rows than a narrower filter, look for a limit or revalidation step overwriting the local IDB result set. Broad views should stay unbounded unless the user is actually narrowing by text.
|
- If a broad Dexie-backed list shows fewer rows than a narrower filter, look for a limit or revalidation step overwriting the local IDB result set. Broad views should stay unbounded unless the user is actually narrowing by text.
|
||||||
- Ensure your `liveQuery` closures return quickly and do not throw; any exception inside the query can stop updates.
|
- Ensure your `liveQuery` closures return quickly and do not throw; any exception inside the query can stop updates.
|
||||||
- If a dependent query appears stale, temporarily add `await 0` in the upstream query or an explicit `Promise.resolve()` after the IDB write to force the microtask queue to flush during debugging.
|
- If a dependent query appears stale, temporarily add `await 0` in the upstream query or an explicit `Promise.resolve()` after the IDB write to force the microtask queue to flush during debugging.
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
|
import { AE_IDAA_LOC_VERSION } from '$lib/stores/store_versions';
|
||||||
import { persisted } from 'svelte-persisted-store';
|
import { persisted } from 'svelte-persisted-store';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
|
|
||||||
const ver = '2024-08-21_1646';
|
|
||||||
|
|
||||||
/* *** BEGIN *** Initialize idaa_local_data_struct */
|
/* *** BEGIN *** Initialize idaa_local_data_struct */
|
||||||
// Persisted to localStorage. Retains Novi identity, auth state, and IDAA
|
// Persisted to localStorage. Retains Novi identity, auth state, and IDAA
|
||||||
// query preferences across sessions. Wiped on schema change via store_versions.ts.
|
// query preferences across sessions. Wiped on schema change via store_versions.ts.
|
||||||
const idaa_local_data_struct: key_val = {
|
const idaa_local_data_struct: key_val = {
|
||||||
ver: ver,
|
__version: AE_IDAA_LOC_VERSION,
|
||||||
|
|
||||||
name: 'Aether - IDAA',
|
name: 'Aether - IDAA',
|
||||||
title: `OSIT's Æ IDAA`,
|
title: `OSIT's Æ IDAA`,
|
||||||
@@ -111,7 +110,6 @@ export const idaa_loc: Writable<key_val> = persisted(
|
|||||||
/* *** BEGIN *** Initialize idaa_session_data_struct */
|
/* *** BEGIN *** Initialize idaa_session_data_struct */
|
||||||
// In-memory only (not persisted). Resets on page load.
|
// In-memory only (not persisted). Resets on page load.
|
||||||
const idaa_session_data_struct: key_val = {
|
const idaa_session_data_struct: key_val = {
|
||||||
ver: ver,
|
|
||||||
log_lvl: 1,
|
log_lvl: 1,
|
||||||
|
|
||||||
archives: {
|
archives: {
|
||||||
@@ -137,6 +135,7 @@ const idaa_session_data_struct: key_val = {
|
|||||||
recovery_meetings: {
|
recovery_meetings: {
|
||||||
qry__status: null,
|
qry__status: null,
|
||||||
qry__fulltext_str: null,
|
qry__fulltext_str: null,
|
||||||
|
search_version: 0,
|
||||||
|
|
||||||
edit__event_obj: null,
|
edit__event_obj: null,
|
||||||
|
|
||||||
@@ -185,7 +184,7 @@ const idaa_trig_template: key_val = {
|
|||||||
event_id: false,
|
event_id: false,
|
||||||
post_id: false
|
post_id: false
|
||||||
};
|
};
|
||||||
export const idaa_trig: any = writable(idaa_trig_template);
|
export const idaa_trig: Writable<key_val> = writable(idaa_trig_template);
|
||||||
|
|
||||||
// Promise map — keyed by object type; used to track in-flight async operations.
|
// Promise map — keyed by object type; used to track in-flight async operations.
|
||||||
const idaa_prom_template: key_val = {
|
const idaa_prom_template: key_val = {
|
||||||
@@ -194,4 +193,4 @@ const idaa_prom_template: key_val = {
|
|||||||
event_id: false,
|
event_id: false,
|
||||||
post_id: false
|
post_id: false
|
||||||
};
|
};
|
||||||
export const idaa_prom: any = writable(idaa_prom_template);
|
export const idaa_prom: Writable<key_val> = writable(idaa_prom_template);
|
||||||
|
|||||||
@@ -31,16 +31,15 @@ if (browser) {
|
|||||||
$idaa_slct.event_id = null;
|
$idaa_slct.event_id = null;
|
||||||
window.parent.postMessage({ event_id: null }, '*');
|
window.parent.postMessage({ event_id: null }, '*');
|
||||||
|
|
||||||
// Use versioning instead of boolean to avoid loops
|
// Use a session-scoped trigger so the persisted IDAA profile is not rewritten
|
||||||
if ($idaa_loc.recovery_meetings.search_version === undefined) {
|
// on every page mount. Recovery Meetings only needs this to kick the initial search.
|
||||||
$idaa_loc.recovery_meetings.search_version = 0;
|
$idaa_sess.recovery_meetings.search_version++;
|
||||||
}
|
|
||||||
$idaa_loc.recovery_meetings.search_version++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_id_li: Array<string> = $state([]);
|
let event_id_li: Array<string> = $state([]);
|
||||||
let search_debounce_timer: any = null;
|
let search_debounce_timer: any = null;
|
||||||
let last_search_id = 0;
|
let last_search_id = 0;
|
||||||
|
let last_executed_key = '';
|
||||||
|
|
||||||
// Standardized Reactive Search Pattern (Aether UI V3)
|
// Standardized Reactive Search Pattern (Aether UI V3)
|
||||||
// This effect manages the orchestration between UI state and data fetching.
|
// This effect manages the orchestration between UI state and data fetching.
|
||||||
@@ -56,7 +55,7 @@ $effect(() => {
|
|||||||
|
|
||||||
// Track filters and the search version (trigger)
|
// Track filters and the search version (trigger)
|
||||||
const qry_params = {
|
const qry_params = {
|
||||||
v: $idaa_loc.recovery_meetings.search_version,
|
v: $idaa_sess.recovery_meetings.search_version,
|
||||||
str: $idaa_loc.recovery_meetings.qry__fulltext_str,
|
str: $idaa_loc.recovery_meetings.qry__fulltext_str,
|
||||||
phys: $idaa_loc.recovery_meetings.qry__physical,
|
phys: $idaa_loc.recovery_meetings.qry__physical,
|
||||||
virt: $idaa_loc.recovery_meetings.qry__virtual,
|
virt: $idaa_loc.recovery_meetings.qry__virtual,
|
||||||
@@ -65,13 +64,14 @@ $effect(() => {
|
|||||||
order: $idaa_loc.recovery_meetings.qry__order_by,
|
order: $idaa_loc.recovery_meetings.qry__order_by,
|
||||||
remote: $idaa_loc.recovery_meetings.qry__remote_first
|
remote: $idaa_loc.recovery_meetings.qry__remote_first
|
||||||
};
|
};
|
||||||
|
const qry_key = JSON.stringify(qry_params);
|
||||||
|
|
||||||
// 2. Debounce Logic
|
// 2. Debounce Logic
|
||||||
if (search_debounce_timer) clearTimeout(search_debounce_timer);
|
if (search_debounce_timer) clearTimeout(search_debounce_timer);
|
||||||
search_debounce_timer = setTimeout(() => {
|
search_debounce_timer = setTimeout(() => {
|
||||||
// 3. Execution (Untracked to prevent loops)
|
// 3. Execution (Untracked to prevent loops)
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
handle_search_refresh();
|
handle_search_refresh(qry_key);
|
||||||
});
|
});
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
@@ -85,7 +85,10 @@ $effect(() => {
|
|||||||
*
|
*
|
||||||
* GOAL: Render matching meetings in < 50ms, then update with perfect server data.
|
* GOAL: Render matching meetings in < 50ms, then update with perfect server data.
|
||||||
*/
|
*/
|
||||||
async function handle_search_refresh() {
|
async function handle_search_refresh(qry_key: string) {
|
||||||
|
if (qry_key === last_executed_key) return;
|
||||||
|
last_executed_key = qry_key;
|
||||||
|
|
||||||
const current_search_id = ++last_search_id;
|
const current_search_id = ++last_search_id;
|
||||||
const account_id = $ae_loc.account_id;
|
const account_id = $ae_loc.account_id;
|
||||||
const remote_first = $idaa_loc.recovery_meetings.qry__remote_first;
|
const remote_first = $idaa_loc.recovery_meetings.qry__remote_first;
|
||||||
|
|||||||
@@ -69,10 +69,7 @@ if (
|
|||||||
* debounced search cycle automatically.
|
* debounced search cycle automatically.
|
||||||
*/
|
*/
|
||||||
function handle_search_trigger() {
|
function handle_search_trigger() {
|
||||||
if ($idaa_loc.recovery_meetings.search_version === undefined) {
|
$idaa_sess.recovery_meetings.search_version++;
|
||||||
$idaa_loc.recovery_meetings.search_version = 0;
|
|
||||||
}
|
|
||||||
$idaa_loc.recovery_meetings.search_version++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||||
|
|||||||
Reference in New Issue
Block a user