diff --git a/.jules/bolt.md b/.jules/bolt.md index 9154160..ee36638 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,11 +1,3 @@ -# Bolt's Journal - -## 2024-05-22 - Async Race Conditions - -**Learning:** Asynchronous typeahead searches must implement a request ID mechanism. Without it, stale responses can overwrite newer ones, leading to correct search terms displaying incorrect results. -**Action:** Always use a request ID or cancellation token pattern when implementing async search/filter operations. - -## 2024-10-25 - Svelte Input Debouncing - -**Learning:** Using `on:keyup` for search input debouncing triggers unnecessary API calls on navigation keys (arrows, home, end) and misses changes from paste/cut. Svelte's reactive statements `$: debounce(value)` provide a robust, declarative way to trigger debouncing only when the value actually changes. -**Action:** Replace `on:keyup` handlers with reactive statements for input debouncing to improve performance and correctness. +## 2024-10-24 - Async Search Caching +**Learning:** Adding a simple cache to async search inputs requires handling race conditions carefully. Specifically, manually triggering search (e.g., submit) should invalidate pending debounce timers to prevent double fetches. +**Action:** Always clear pending timers at the start of the async function execution, not just in the debounce wrapper. diff --git a/src/routes/DomainSearch.svelte b/src/routes/DomainSearch.svelte index 53d006f..1d6bedf 100644 --- a/src/routes/DomainSearch.svelte +++ b/src/routes/DomainSearch.svelte @@ -17,6 +17,8 @@ let isLoading: boolean = false; let debounceTimer: ReturnType; let requestId = 0; + // Cache for domain search results to prevent redundant API calls + let cache = new Map(); $: errors = invalid ? validator.getErrors() : []; $: invalid = domainName !== '' && !validator.validate(domainName, { raiseError: false }); @@ -33,15 +35,34 @@ async function search(submit = false) { if (invalid) return; + // Clear pending debounce timer to prevent double fetches on manual submit + clearTimeout(debounceTimer); + if (domainName === '') return; - if (submit && domainName === nameSearched) { + + const normalizedDomain = domainName.toLocaleLowerCase(); + + if (submit && normalizedDomain === nameSearched) { const url = domain ? `/domain/${nameSearched}` : `/register/${nameSearched}`; return goto(url); } + // Return cached result if available + if (cache.has(normalizedDomain)) { + domain = cache.get(normalizedDomain); + nameSearched = normalizedDomain; + isLoading = false; + + if (submit) { + const url = domain ? `/domain/${nameSearched}` : `/register/${nameSearched}`; + return goto(url); + } + return; + } + const currentRequestId = ++requestId; - nameSearched = domainName.toLocaleLowerCase(); + nameSearched = normalizedDomain; isLoading = true; const result = await $metaNamesSdk.domainRepository.find(domainName); @@ -49,6 +70,7 @@ if (currentRequestId === requestId) { domain = result; isLoading = false; + cache.set(normalizedDomain, result); } }