Fix search not working

This commit is contained in:
Timothy Farrell 2026-05-15 23:52:39 +00:00
parent f229bb978e
commit a6589fb1f3
7 changed files with 60 additions and 19 deletions

View File

@ -31,7 +31,7 @@ src/
## Key Design Decisions ## 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. - **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. - **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. - **Single-file build**`vite-plugin-singlefile` inlines all JS/CSS; post-build script inlines favicon.

34
dist/index.html vendored
View File

@ -5763,6 +5763,7 @@ function LockScreen($$anchor, $$props) {
} }
//#endregion //#endregion
//#region src/lib/stores/search.svelte.js //#region src/lib/stores/search.svelte.js
var DEBOUNCE_MS = 300;
var SearchStore = class { var SearchStore = class {
#query = /* @__PURE__ */ state(""); #query = /* @__PURE__ */ state("");
get query() { get query() {
@ -5771,6 +5772,13 @@ var SearchStore = class {
set query(value) { set query(value) {
set(this.#query, value, true); 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"); #activeGroupId = /* @__PURE__ */ state("all");
get activeGroupId() { get activeGroupId() {
return get(this.#activeGroupId); return get(this.#activeGroupId);
@ -5785,8 +5793,21 @@ var SearchStore = class {
set refreshTrigger(value) { set refreshTrigger(value) {
set(this.#refreshTrigger, value, true); 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() { clear() {
this.query = ""; this.query = "";
this.debouncedQuery = "";
this.activeGroupId = "all"; this.activeGroupId = "all";
} }
/** Force subscribed components to re-fetch data. */ /** Force subscribed components to re-fetch data. */
@ -6007,13 +6028,13 @@ function Sidebar($$anchor, $$props) {
set_value(input, search.query); set_value(input, search.query);
set_class(button, 1, `group-item ${search.activeGroupId === "all" ? "active" : ""}`, "svelte-181dlmc"); 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, () => search.activeGroupId = "all");
delegated("click", button_4, () => openGroupForm(null)); delegated("click", button_4, () => openGroupForm(null));
append($$anchor, div); append($$anchor, div);
pop(); pop();
} }
delegate(["click"]); delegate(["input", "click"]);
//#endregion //#endregion
//#region src/components/EntryList.svelte //#region src/components/EntryList.svelte
var root_1$6 = /* @__PURE__ */ from_html(`<div class="loading svelte-13s7gu4">Loading entries...</div>`); var root_1$6 = /* @__PURE__ */ from_html(`<div class="loading svelte-13s7gu4">Loading entries...</div>`);
@ -6034,7 +6055,7 @@ function EntryList($$anchor, $$props) {
set(loading, true); set(loading, true);
set(error, ""); set(error, "");
try { try {
const query = search.query.trim(); const query = search.debouncedQuery.trim();
const groupId = search.activeGroupId; const groupId = search.activeGroupId;
if (query) set(entries, await searchEntries(query, groupId !== "all" ? { groupId } : {}), true); if (query) set(entries, await searchEntries(query, groupId !== "all" ? { groupId } : {}), true);
else if (groupId !== "all") set(entries, await getEntries({ groupId }), true); else if (groupId !== "all") set(entries, await getEntries({ groupId }), true);
@ -6046,7 +6067,7 @@ function EntryList($$anchor, $$props) {
set(loading, false); set(loading, false);
} }
user_effect(() => { user_effect(() => {
search.query; search.debouncedQuery;
search.activeGroupId; search.activeGroupId;
search.refreshTrigger; search.refreshTrigger;
loadEntries(); loadEntries();
@ -6883,10 +6904,7 @@ function MainLayout($$anchor, $$props) {
if (get(sidebarOpen)) $$render(consequent); if (get(sidebarOpen)) $$render(consequent);
}); });
var aside = sibling(node, 2); var aside = sibling(node, 2);
Sidebar(child(aside), { $$events: { Sidebar(child(aside), {});
back: handleBack,
goList
} });
reset(aside); reset(aside);
var main = sibling(aside, 2); var main = sibling(aside, 2);
var div_2 = child(main); var div_2 = child(main);

View File

@ -13,7 +13,7 @@
loading = true loading = true
error = '' error = ''
try { try {
const query = searchStore.query.trim() const query = searchStore.debouncedQuery.trim()
const groupId = searchStore.activeGroupId const groupId = searchStore.activeGroupId
if (query) { if (query) {
@ -35,9 +35,9 @@
loading = false 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(() => { $effect(() => {
searchStore.query searchStore.debouncedQuery
searchStore.activeGroupId searchStore.activeGroupId
searchStore.refreshTrigger searchStore.refreshTrigger
loadEntries() loadEntries()

View File

@ -58,7 +58,7 @@
<!-- Sidebar --> <!-- Sidebar -->
<aside class="sidebar {sidebarOpen ? 'open' : ''}"> <aside class="sidebar {sidebarOpen ? 'open' : ''}">
<Sidebar on:back={handleBack} on:goList={goList} /> <Sidebar />
</aside> </aside>
<!-- Main content --> <!-- Main content -->

View File

@ -96,7 +96,7 @@
type="text" type="text"
placeholder="Search entries..." placeholder="Search entries..."
value={searchStore.query} value={searchStore.query}
onInput={(e) => searchStore.query = e.target.value} oninput={(e) => searchStore.setSearchQuery(e.target.value)}
/> />
</div> </div>

View File

@ -3,13 +3,36 @@
* Shared between Sidebar and EntryList for coordinated filtering. * Shared between Sidebar and EntryList for coordinated filtering.
*/ */
const DEBOUNCE_MS = 300
export class SearchStore { 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 activeGroupId = $state('all') // 'all' or a group id
refreshTrigger = $state(0) // incremented to force a re-fetch 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() { clear() {
this.query = '' this.query = ''
this.debouncedQuery = ''
this.activeGroupId = 'all' this.activeGroupId = 'all'
} }

View File

@ -18,12 +18,12 @@ describe('SearchStore', () => {
describe('query', () => { describe('query', () => {
it('should update query', () => { it('should update query', () => {
search.query = 'test' search.setSearchQuery('test')
expect(search.query).toBe('test') expect(search.query).toBe('test')
}) })
it('should handle unicode query', () => { it('should handle unicode query', () => {
search.query = 'тест' search.setSearchQuery('тест')
expect(search.query).toBe('тест') expect(search.query).toBe('тест')
}) })
}) })
@ -48,7 +48,7 @@ describe('SearchStore', () => {
describe('clear()', () => { describe('clear()', () => {
it('should reset query to empty string', () => { it('should reset query to empty string', () => {
search.query = 'some search' search.setSearchQuery('some search')
search.clear() search.clear()
expect(search.query).toBe('') expect(search.query).toBe('')
}) })
@ -60,7 +60,7 @@ describe('SearchStore', () => {
}) })
it('should reset both fields simultaneously', () => { it('should reset both fields simultaneously', () => {
search.query = 'some search' search.setSearchQuery('some search')
search.activeGroupId = 'group-123' search.activeGroupId = 'group-123'
search.clear() search.clear()
expect(search.query).toBe('') expect(search.query).toBe('')