astro-tractstack 2.0.0-rc.45 → 2.0.0-rc.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -205,26 +205,22 @@ export default function SearchModal({
|
|
|
205
205
|
)}
|
|
206
206
|
|
|
207
207
|
{!showResults && (
|
|
208
|
-
<div className="relative">
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
className="text-mydarkgrey relative z-10 w-full border-none bg-transparent text-xl placeholder-gray-500 outline-none"
|
|
225
|
-
style={{ background: 'transparent' }}
|
|
226
|
-
/>
|
|
227
|
-
</div>
|
|
208
|
+
<div className="relative w-full px-6 py-2">
|
|
209
|
+
{showCompletion && (
|
|
210
|
+
<div className="pointer-events-none absolute left-0 top-0 flex h-full w-full items-center px-6 py-2 text-xl text-gray-400">
|
|
211
|
+
{bestCompletion}
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
<input
|
|
215
|
+
ref={inputRef}
|
|
216
|
+
type="text"
|
|
217
|
+
value={query}
|
|
218
|
+
onChange={handleInputChange}
|
|
219
|
+
onKeyDown={handleKeyDown}
|
|
220
|
+
placeholder="Search content..."
|
|
221
|
+
className="text-mydarkgrey relative z-10 w-full border-none bg-transparent text-xl placeholder-gray-500 outline-none"
|
|
222
|
+
style={{ background: 'transparent', padding: '0' }}
|
|
223
|
+
/>
|
|
228
224
|
</div>
|
|
229
225
|
)}
|
|
230
226
|
<button
|
|
@@ -342,7 +338,6 @@ export default function SearchModal({
|
|
|
342
338
|
<SearchResults
|
|
343
339
|
results={searchResults}
|
|
344
340
|
contentMap={contentMap}
|
|
345
|
-
onResultClick={handleClose}
|
|
346
341
|
/>
|
|
347
342
|
)}
|
|
348
343
|
</div>
|
|
@@ -14,7 +14,6 @@ const VERBOSE = false;
|
|
|
14
14
|
interface SearchResultsProps {
|
|
15
15
|
results: CategorizedResults;
|
|
16
16
|
contentMap: FullContentMapItem[];
|
|
17
|
-
onResultClick: () => void;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
interface ResultItem {
|
|
@@ -36,7 +35,6 @@ const ITEMS_PER_PAGE = 10;
|
|
|
36
35
|
export default function SearchResults({
|
|
37
36
|
results,
|
|
38
37
|
contentMap,
|
|
39
|
-
onResultClick,
|
|
40
38
|
}: SearchResultsProps) {
|
|
41
39
|
const [currentPage, setCurrentPage] = useState(1);
|
|
42
40
|
|
|
@@ -238,7 +236,7 @@ export default function SearchResults({
|
|
|
238
236
|
key={item.id}
|
|
239
237
|
className="rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-100"
|
|
240
238
|
>
|
|
241
|
-
<a href={item.url}
|
|
239
|
+
<a href={item.url} className="group block">
|
|
242
240
|
<div className="flex flex-col md:flex-row md:items-start md:gap-4">
|
|
243
241
|
{/* Mobile: Full width image with overlay badge */}
|
|
244
242
|
<div
|
|
@@ -39,10 +39,12 @@ export function useSearch(): UseSearchReturn {
|
|
|
39
39
|
const [isRetrieving, setIsRetrieving] = useState(false);
|
|
40
40
|
const [retrieveError, setRetrieveError] = useState<string | null>(null);
|
|
41
41
|
|
|
42
|
+
// Race condition protection
|
|
42
43
|
const debounceRef = useRef<NodeJS.Timeout>();
|
|
43
44
|
const lastSearchTimeRef = useRef<number>(0);
|
|
44
45
|
const pendingQueryRef = useRef<string | null>(null);
|
|
45
46
|
const throttleTimeoutRef = useRef<NodeJS.Timeout>();
|
|
47
|
+
const inflightQueryRef = useRef<string | null>(null);
|
|
46
48
|
const api = useMemo(() => new TractStackAPI(), []);
|
|
47
49
|
|
|
48
50
|
const performDiscovery = useCallback(
|
|
@@ -52,26 +54,43 @@ export function useSearch(): UseSearchReturn {
|
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
// Check if this exact query is already inflight
|
|
58
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Mark this query as inflight
|
|
63
|
+
inflightQueryRef.current = query.trim();
|
|
64
|
+
|
|
55
65
|
setIsDiscovering(true);
|
|
56
66
|
setDiscoverError(null);
|
|
57
|
-
lastSearchTimeRef.current = Date.now();
|
|
58
67
|
|
|
59
68
|
try {
|
|
60
69
|
const response = await api.discover(query.trim());
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
// Only process response if this is still the current inflight query
|
|
72
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
73
|
+
if (response.success && response.data) {
|
|
74
|
+
setSuggestions(response.data.suggestions);
|
|
75
|
+
} else {
|
|
76
|
+
setDiscoverError(response.error || 'Discovery failed');
|
|
77
|
+
setSuggestions([]);
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
} catch (err) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
// Only process error if this is still the current inflight query
|
|
82
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
83
|
+
setDiscoverError(
|
|
84
|
+
err instanceof Error ? err.message : 'Discovery failed'
|
|
85
|
+
);
|
|
86
|
+
setSuggestions([]);
|
|
87
|
+
}
|
|
73
88
|
} finally {
|
|
74
|
-
|
|
89
|
+
// Clear inflight tracking only if this is still the current query
|
|
90
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
91
|
+
inflightQueryRef.current = null;
|
|
92
|
+
setIsDiscovering(false);
|
|
93
|
+
}
|
|
75
94
|
}
|
|
76
95
|
},
|
|
77
96
|
[api]
|
|
@@ -107,6 +126,10 @@ export function useSearch(): UseSearchReturn {
|
|
|
107
126
|
if (pendingQueryRef.current) {
|
|
108
127
|
const queryToExecute = pendingQueryRef.current;
|
|
109
128
|
pendingQueryRef.current = null;
|
|
129
|
+
|
|
130
|
+
// Update timestamp immediately when scheduling request, not when executing
|
|
131
|
+
lastSearchTimeRef.current = Date.now();
|
|
132
|
+
|
|
110
133
|
performDiscovery(queryToExecute);
|
|
111
134
|
}
|
|
112
135
|
}, [performDiscovery]);
|
|
@@ -133,6 +156,7 @@ export function useSearch(): UseSearchReturn {
|
|
|
133
156
|
setDiscoverError(null);
|
|
134
157
|
setIsDiscovering(false);
|
|
135
158
|
pendingQueryRef.current = null;
|
|
159
|
+
inflightQueryRef.current = null;
|
|
136
160
|
return;
|
|
137
161
|
}
|
|
138
162
|
|
|
@@ -199,7 +223,7 @@ export function useSearch(): UseSearchReturn {
|
|
|
199
223
|
clearTimeout(throttleTimeoutRef.current);
|
|
200
224
|
}
|
|
201
225
|
|
|
202
|
-
// Reset all state
|
|
226
|
+
// Reset all state including race condition protection
|
|
203
227
|
setSuggestions([]);
|
|
204
228
|
setIsDiscovering(false);
|
|
205
229
|
setDiscoverError(null);
|
|
@@ -207,6 +231,7 @@ export function useSearch(): UseSearchReturn {
|
|
|
207
231
|
setIsRetrieving(false);
|
|
208
232
|
setRetrieveError(null);
|
|
209
233
|
pendingQueryRef.current = null;
|
|
234
|
+
inflightQueryRef.current = null;
|
|
210
235
|
}, []);
|
|
211
236
|
|
|
212
237
|
return {
|
|
@@ -273,8 +273,8 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
273
273
|
}
|
|
274
274
|
<div
|
|
275
275
|
id="loading-indicator"
|
|
276
|
-
class="bg-myorange fixed left-0 top-0
|
|
277
|
-
style="opacity: 0.5; filter: blur(0.5px);"
|
|
276
|
+
class="bg-myorange fixed left-0 top-0 h-1 w-full scale-x-0 transform transition-transform duration-300 ease-out"
|
|
277
|
+
style="opacity: 0.5; filter: blur(0.5px); z-index: 10070;"
|
|
278
278
|
>
|
|
279
279
|
</div>
|
|
280
280
|
|