docs: document bootstrap account_id race condition and liveQuery stale-record pattern
Adds entry #14 to BOOTSTRAP__AI_Agent_Quickstart.md (section 7 "Mistakes
Agents Have Made") and a new "Bootstrap Race" subsection to
GUIDE__SvelteKit2_Svelte5_DexieJS.md ("Common Gotchas"), capturing the
fix from 5fce14980: gate account-scoped liveQuery triggers on
$slct.account_id (non-persisted), not $ae_loc.account_id (persisted,
potentially stale), and treat IDB records from a different non-null
account as a cache miss. Also fixes five pre-existing MD049 emphasis
style warnings (asterisk → underscore) in the Dexie guide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -397,6 +397,55 @@ These are real incidents — know them before you start.
|
||||
- `timeout = 20000` default (was 60s in PATCH/DELETE until 2026-05-21 — 5-min worst case)
|
||||
- `did_timeout_abort` flag per attempt (separates helper timeouts from caller aborts)
|
||||
|
||||
14. **Account-scoped `liveQuery` trigger firing before bootstrap completes** — components
|
||||
that load account-specific data via `liveQuery` must not trigger the API fetch until the
|
||||
bootstrap Sync Effect in `+layout.svelte` has set the real `account_id`.
|
||||
|
||||
**What happened:** `element_data_store.svelte` triggered its load when `entry` was falsy.
|
||||
On a fresh load with no IDB cache, `$ae_api.account_id` was still `null` (bootstrap hadn't
|
||||
run yet). The `localStorage` scavenge in `api_get_object.ts` then read the stale
|
||||
`account_id = 1` from a previous dev/demo session and made the API call with the wrong
|
||||
account. The response was cached in IDB, and the next page load showed the wrong account's
|
||||
record.
|
||||
|
||||
A second failure mode: if IDB _did_ have a cached record from a previous session with a
|
||||
different account, `liveQuery` returned it as a valid hit (`entry` truthy), so the trigger
|
||||
never fired to fetch the correct record.
|
||||
|
||||
**The fix pattern** for any trigger `$effect` that depends on bootstrapped account context:
|
||||
```typescript
|
||||
$effect(() => {
|
||||
// Use $slct.account_id (non-persisted), NOT $ae_loc.account_id (persisted, stale).
|
||||
// $slct is initialized to null and set only by the bootstrap Sync Effect, so it
|
||||
// reliably gates the fetch until bootstrap has completed.
|
||||
const account_id = $slct.account_id;
|
||||
const api_ready = !!$ae_api?.base_url;
|
||||
const entry = $lq__ds_obj as SomeType | null | undefined;
|
||||
|
||||
if (!browser || !account_id || !api_ready) return;
|
||||
|
||||
// Also re-fetch when IDB holds a record from a different (non-null) account.
|
||||
// null account_id = global/shared fallback — that is still a valid cache hit.
|
||||
const entry_is_stale_account =
|
||||
entry !== undefined &&
|
||||
entry !== null &&
|
||||
entry.account_id !== null &&
|
||||
entry.account_id !== account_id;
|
||||
|
||||
if (!entry || entry_is_stale_account) {
|
||||
trigger = 'load...';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Why `$slct` not `$ae_loc`:**
|
||||
`$ae_loc` is a `svelte-persisted-store` — it hydrates from `localStorage` before any
|
||||
effects run, so its `account_id` may be a stale value from a previous session. `$slct`
|
||||
is a plain writable store initialized to `null`; the bootstrap Sync Effect is the only
|
||||
thing that sets it. Until that runs, `$slct.account_id` is `null`, providing a reliable
|
||||
gate. See `GUIDE__SvelteKit2_Svelte5_DexieJS.md` → "Bootstrap Race" for the Dexie-side
|
||||
context.
|
||||
|
||||
---
|
||||
|
||||
## 8. Source Layout (Quick Reference)
|
||||
|
||||
Reference in New Issue
Block a user