docs: add IDAA auth test lessons and untrack() reactive tracking guide
tests/README.md — new "IDAA Auth Tests" section with three lessons:
1. ae_idaa_loc seed must include full bb/archives structure or
verify_novi_uuid() throws silently and resets novi_uuid to null
2. StorageEvent pattern for testing reactive persisted-store updates
without pre-seeding Dexie or navigating twice
3. getByText { exact: false } for UUID in multi-field spans
GUIDE__SvelteKit2_Svelte5_DexieJS.md — new "untrack() reactive tracking
trap" section: reading a store value inside untrack() makes it a one-shot
dependency; fix is to hoist the read outside untrack() and add a guard
to avoid redundant work on unrelated store updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -280,6 +280,66 @@ If you must use non-blocking loads, you must pass the initial data to the compon
|
||||
{/if}
|
||||
```
|
||||
|
||||
## The `untrack()` Reactive-Tracking Trap
|
||||
|
||||
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you *need* to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
|
||||
|
||||
### Symptom
|
||||
|
||||
An effect runs once, reads a store value inside `untrack()`, takes an early-exit path (e.g. "no API key → skip"), and never retries — even after the store value is updated by a background process.
|
||||
|
||||
### Real Example (IDAA Novi Verification Bug — 2026-03-25)
|
||||
|
||||
The IDAA layout verifies Novi UUIDs. `site_cfg_json` (which contains the Novi API key) was read **inside** `untrack()`:
|
||||
|
||||
```typescript
|
||||
// BUG: site_cfg_json read inside untrack → one-shot, never retries
|
||||
$effect(() => {
|
||||
if (!browser) return;
|
||||
const uuid = data.url.searchParams.get('uuid'); // tracked ✓
|
||||
|
||||
untrack(() => {
|
||||
const site_cfg_json = $ae_loc.site_cfg_json; // ← NOT tracked ✗
|
||||
const api_key = site_cfg_json?.novi_idaa_api_key ?? null;
|
||||
if (!api_key) return; // exits silently on first load with stale cache
|
||||
verify_novi_uuid(uuid, api_key, ...);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
On first load, the Dexie cache returned a stale `site_cfg_json` missing the API key. The effect exited early. The background refresh later updated `$ae_loc.site_cfg_json`, but because `site_cfg_json` was consumed inside `untrack()`, the effect never re-ran.
|
||||
|
||||
**Fix:** Move the dependency read **outside** `untrack()`:
|
||||
|
||||
```typescript
|
||||
// FIX: site_cfg_json tracked outside untrack → effect re-runs when it changes
|
||||
$effect(() => {
|
||||
if (!browser) return;
|
||||
const uuid = data.url.searchParams.get('uuid'); // tracked ✓
|
||||
const site_cfg_json = $ae_loc.site_cfg_json; // tracked ✓ — effect re-runs on change
|
||||
|
||||
untrack(() => {
|
||||
// Guard: already verified for this UUID — don't repeat the round-trip
|
||||
if ($idaa_loc.novi_verified && $idaa_loc.novi_uuid === uuid) return;
|
||||
|
||||
const api_key = site_cfg_json?.novi_idaa_api_key ?? null;
|
||||
if (!api_key) return;
|
||||
verify_novi_uuid(uuid, api_key, ...);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The guard inside `untrack()` is important: without it, every unrelated change to `$ae_loc` would re-trigger verification.
|
||||
|
||||
### Rule of Thumb
|
||||
|
||||
Before wrapping a store read in `untrack()`, ask: **"Do I need this effect to re-run if this value changes?"**
|
||||
|
||||
- If yes → read it **outside** `untrack()`, and add a guard inside to prevent redundant work.
|
||||
- If no → `untrack()` is correct.
|
||||
|
||||
---
|
||||
|
||||
## Svelte 5 Binding Pitfalls
|
||||
|
||||
### 1. `props_invalid_value` (The "Expression Binding" Error)
|
||||
|
||||
Reference in New Issue
Block a user