From a6589fb1f311643bab13a8b6d925ca101ee56558 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Fri, 15 May 2026 23:52:39 +0000 Subject: [PATCH] Fix search not working --- AGENTS.md | 2 +- dist/index.html | 34 ++++++++++++++++++++++++-------- src/components/EntryList.svelte | 6 +++--- src/components/MainLayout.svelte | 2 +- src/components/Sidebar.svelte | 2 +- src/lib/stores/search.svelte.js | 25 ++++++++++++++++++++++- tests/lib/stores/search.test.js | 8 ++++---- 7 files changed, 60 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0392f3e..84f9100 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ src/ ## Key Design Decisions -- **Svelte 5 runes** — Use `$state`, `$derived`, `$effect`. Props-based event passing (no Svelte events). +- **Svelte 5 runes** — Use `$state`, `$derived`, `$effect`. Props-based event passing (no Svelte events). Event handler attributes are all lower-case (e.g. oninput) - **No external crypto libraries** — Uses the browser's native Web Crypto API exclusively. - **Key never persisted** — Encryption key lives only in `$state` memory; cleared on lock, tab switch, or page close. - **Single-file build** — `vite-plugin-singlefile` inlines all JS/CSS; post-build script inlines favicon. diff --git a/dist/index.html b/dist/index.html index 14c0c6e..7b2549d 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5763,6 +5763,7 @@ function LockScreen($$anchor, $$props) { } //#endregion //#region src/lib/stores/search.svelte.js +var DEBOUNCE_MS = 300; var SearchStore = class { #query = /* @__PURE__ */ state(""); get query() { @@ -5771,6 +5772,13 @@ var SearchStore = class { set query(value) { set(this.#query, value, true); } + #debouncedQuery = /* @__PURE__ */ state(""); + get debouncedQuery() { + return get(this.#debouncedQuery); + } + set debouncedQuery(value) { + set(this.#debouncedQuery, value, true); + } #activeGroupId = /* @__PURE__ */ state("all"); get activeGroupId() { return get(this.#activeGroupId); @@ -5785,8 +5793,21 @@ var SearchStore = class { set refreshTrigger(value) { set(this.#refreshTrigger, value, true); } + #debounceTimer = null; + setSearchQuery(value) { + this.query = value; + if (this.#debounceTimer) clearTimeout(this.#debounceTimer); + if (value === "") { + this.debouncedQuery = ""; + this.#debounceTimer = null; + } else this.#debounceTimer = setTimeout(() => { + this.debouncedQuery = value; + this.#debounceTimer = null; + }, DEBOUNCE_MS); + } clear() { this.query = ""; + this.debouncedQuery = ""; this.activeGroupId = "all"; } /** Force subscribed components to re-fetch data. */ @@ -6007,13 +6028,13 @@ function Sidebar($$anchor, $$props) { set_value(input, search.query); set_class(button, 1, `group-item ${search.activeGroupId === "all" ? "active" : ""}`, "svelte-181dlmc"); }); - event("Input", input, (e) => search.query = e.target.value); + delegated("input", input, (e) => search.setSearchQuery(e.target.value)); delegated("click", button, () => search.activeGroupId = "all"); delegated("click", button_4, () => openGroupForm(null)); append($$anchor, div); pop(); } -delegate(["click"]); +delegate(["input", "click"]); //#endregion //#region src/components/EntryList.svelte var root_1$6 = /* @__PURE__ */ from_html(`
Loading entries...
`); @@ -6034,7 +6055,7 @@ function EntryList($$anchor, $$props) { set(loading, true); set(error, ""); try { - const query = search.query.trim(); + const query = search.debouncedQuery.trim(); const groupId = search.activeGroupId; if (query) set(entries, await searchEntries(query, groupId !== "all" ? { groupId } : {}), true); else if (groupId !== "all") set(entries, await getEntries({ groupId }), true); @@ -6046,7 +6067,7 @@ function EntryList($$anchor, $$props) { set(loading, false); } user_effect(() => { - search.query; + search.debouncedQuery; search.activeGroupId; search.refreshTrigger; loadEntries(); @@ -6883,10 +6904,7 @@ function MainLayout($$anchor, $$props) { if (get(sidebarOpen)) $$render(consequent); }); var aside = sibling(node, 2); - Sidebar(child(aside), { $$events: { - back: handleBack, - goList - } }); + Sidebar(child(aside), {}); reset(aside); var main = sibling(aside, 2); var div_2 = child(main); diff --git a/src/components/EntryList.svelte b/src/components/EntryList.svelte index 9a3842b..9acff08 100644 --- a/src/components/EntryList.svelte +++ b/src/components/EntryList.svelte @@ -13,7 +13,7 @@ loading = true error = '' try { - const query = searchStore.query.trim() + const query = searchStore.debouncedQuery.trim() const groupId = searchStore.activeGroupId if (query) { @@ -35,9 +35,9 @@ loading = false } - // Reload when search query, active group filter, or refresh trigger changes + // Reload when debounced search query, active group filter, or refresh trigger changes $effect(() => { - searchStore.query + searchStore.debouncedQuery searchStore.activeGroupId searchStore.refreshTrigger loadEntries() diff --git a/src/components/MainLayout.svelte b/src/components/MainLayout.svelte index 971a1cb..0b29cc1 100644 --- a/src/components/MainLayout.svelte +++ b/src/components/MainLayout.svelte @@ -58,7 +58,7 @@ diff --git a/src/components/Sidebar.svelte b/src/components/Sidebar.svelte index c82578e..02c39c5 100644 --- a/src/components/Sidebar.svelte +++ b/src/components/Sidebar.svelte @@ -96,7 +96,7 @@ type="text" placeholder="Search entries..." value={searchStore.query} - onInput={(e) => searchStore.query = e.target.value} + oninput={(e) => searchStore.setSearchQuery(e.target.value)} /> diff --git a/src/lib/stores/search.svelte.js b/src/lib/stores/search.svelte.js index 49eb32e..c86997f 100644 --- a/src/lib/stores/search.svelte.js +++ b/src/lib/stores/search.svelte.js @@ -3,13 +3,36 @@ * Shared between Sidebar and EntryList for coordinated filtering. */ +const DEBOUNCE_MS = 300 + export class SearchStore { - query = $state('') + query = $state('') // raw input value — bound to the search input + debouncedQuery = $state('') // debounced value — used for actual search activeGroupId = $state('all') // 'all' or a group id refreshTrigger = $state(0) // incremented to force a re-fetch + #debounceTimer = null + + /** + * Update the search query with debouncing. + * Call this from the input handler instead of setting `query` directly. + */ + setSearchQuery(value) { + this.query = value + if (this.#debounceTimer) clearTimeout(this.#debounceTimer) + if (value === '') { + this.debouncedQuery = '' + this.#debounceTimer = null + } else { + this.#debounceTimer = setTimeout(() => { + this.debouncedQuery = value + this.#debounceTimer = null + }, DEBOUNCE_MS) + } + } clear() { this.query = '' + this.debouncedQuery = '' this.activeGroupId = 'all' } diff --git a/tests/lib/stores/search.test.js b/tests/lib/stores/search.test.js index 352cdfc..eb3e916 100644 --- a/tests/lib/stores/search.test.js +++ b/tests/lib/stores/search.test.js @@ -18,12 +18,12 @@ describe('SearchStore', () => { describe('query', () => { it('should update query', () => { - search.query = 'test' + search.setSearchQuery('test') expect(search.query).toBe('test') }) it('should handle unicode query', () => { - search.query = 'тест' + search.setSearchQuery('тест') expect(search.query).toBe('тест') }) }) @@ -48,7 +48,7 @@ describe('SearchStore', () => { describe('clear()', () => { it('should reset query to empty string', () => { - search.query = 'some search' + search.setSearchQuery('some search') search.clear() expect(search.query).toBe('') }) @@ -60,7 +60,7 @@ describe('SearchStore', () => { }) it('should reset both fields simultaneously', () => { - search.query = 'some search' + search.setSearchQuery('some search') search.activeGroupId = 'group-123' search.clear() expect(search.query).toBe('')