From 4b17f19038414174075787047529f0fa0b336778 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:48:50 +0200 Subject: [PATCH] docs(plans): Plan-Doc auf runDebounced() ohne Parameter angleichen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumer-Patterns (Task 3/4) aktualisiert: $effect liest store.query explizit und ruft runDebounced() parameterlos — matcht die live Impl nach Commit 4edddc3. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../plans/2026-04-19-search-state-store.md | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/docs/superpowers/plans/2026-04-19-search-state-store.md b/docs/superpowers/plans/2026-04-19-search-state-store.md index 0b1fc19..9a36b69 100644 --- a/docs/superpowers/plans/2026-04-19-search-state-store.md +++ b/docs/superpowers/plans/2026-04-19-search-state-store.md @@ -54,8 +54,8 @@ export class SearchStore { constructor(opts?: SearchStoreOptions); - /** Call from `$effect(() => store.runDebounced(store.query))`. Handles debounce + race-guard. */ - runDebounced(q: string): void; + /** Call from `$effect(() => { store.query; store.runDebounced(); })`. Handles debounce + race-guard. */ + runDebounced(): void; /** Immediate (no debounce). Used by form `submit`. */ runSearch(q: string): Promise; /** Filter-change re-search — shorter debounce. */ @@ -124,7 +124,7 @@ describe('SearchStore', () => { const fetchImpl = mockFetch([]); const store = new SearchStore({ fetchImpl, debounceMs: 50 }); store.query = 'abc'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(100); expect(store.searching).toBe(false); expect(fetchImpl).not.toHaveBeenCalled(); @@ -137,7 +137,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 50, pageSize: 30 }); store.query = 'pasta'; - store.runDebounced(store.query); + store.runDebounced(); expect(store.searching).toBe(true); await vi.advanceTimersByTimeAsync(100); await vi.waitFor(() => expect(fetchImpl).toHaveBeenCalled()); @@ -155,7 +155,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 50 }); store.query = 'pizza'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(100); await vi.waitFor(() => expect(store.webHits).toHaveLength(1)); expect(fetchImpl).toHaveBeenCalledTimes(2); @@ -170,7 +170,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10 }); store.query = 'stale-query'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); store.query = 'different'; // user kept typing await vi.waitFor(() => expect(fetchImpl).toHaveBeenCalled()); @@ -187,7 +187,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10, pageSize: 30 }); store.query = 'meal'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(store.hits).toHaveLength(30)); expect(store.localExhausted).toBe(false); @@ -209,7 +209,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10, pageSize: 30 }); store.query = 'soup'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(store.hits).toHaveLength(1)); expect(store.localExhausted).toBe(true); @@ -228,7 +228,7 @@ describe('SearchStore', () => { ]); const store = new SearchStore({ fetchImpl, debounceMs: 10 }); store.query = 'anything'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(store.webError).toBe('SearXNG unreachable')); expect(store.webExhausted).toBe(true); @@ -239,7 +239,7 @@ describe('SearchStore', () => { const fetchImpl = mockFetch([]); const store = new SearchStore({ fetchImpl, debounceMs: 100 }); store.query = 'foobar'; - store.runDebounced(store.query); + store.runDebounced(); store.reset(); await vi.advanceTimersByTimeAsync(200); expect(store.query).toBe(''); @@ -264,7 +264,7 @@ describe('SearchStore', () => { store.restoreSnapshot(snap); expect(store.query).toBe('lasagne'); expect(store.hits).toHaveLength(1); - store.runDebounced(store.query); // should NOT re-fetch after restore + store.runDebounced(); // should NOT re-fetch after restore await vi.advanceTimersByTimeAsync(100); expect(fetchImpl).not.toHaveBeenCalled(); const round = store.captureSnapshot(); @@ -283,7 +283,7 @@ describe('SearchStore', () => { filterParam: () => '&domains=chefkoch.de' }); store.query = 'curry'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); await vi.waitFor(() => expect(fetchImpl).toHaveBeenCalledTimes(2)); expect(fetchImpl.mock.calls[0][0]).toMatch(/&domains=chefkoch\.de/); @@ -305,7 +305,7 @@ describe('SearchStore', () => { filterParam: () => filter }); store.query = 'broth'; - store.runDebounced(store.query); + store.runDebounced(); await vi.advanceTimersByTimeAsync(15); // Simulate filter change filter = '&domains=chefkoch.de'; @@ -400,10 +400,11 @@ export class SearchStore { Add to the class: ```ts - runDebounced(_q: string): void { - // Parameter is present so consumers can wire `$effect(() => store.runDebounced(store.query))` - // — the `$effect` needs to read `store.query` to track it. We re-read this.query here - // to stay consistent with the actual live value. + runDebounced(): void { + // Consumer pattern: + // $effect(() => { store.query; store.runDebounced(); }); + // The bare `store.query` read registers the reactive dep; this method + // then reads `this.query` live to kick off / debounce the search. if (this.debounceTimer) clearTimeout(this.debounceTimer); if (this.skipNextDebounce) { this.skipNextDebounce = false; @@ -648,10 +649,11 @@ Remove the local `filterParam()` helper — the store owns it now. ```ts $effect(() => { - navStore.runDebounced(navStore.query); + // Bare reads register the reactive deps; then kick the store. + const q = navStore.query; + navStore.runDebounced(); // navOpen follows query length: open while typing, close when cleared. - if (navStore.query.trim().length > 3) navOpen = true; - else navOpen = false; + navOpen = q.trim().length > 3; }); ``` @@ -795,7 +797,9 @@ Remove lines 188–199 (filter-change effect) and lines 322–347 (query-change Add: ```ts $effect(() => { - store.runDebounced(store.query); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + store.query; // register reactive dep + store.runDebounced(); }); $effect(() => {