astro-tractstack 2.0.0-rc.46 → 2.0.0-rc.47

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.0-rc.46",
3
+ "version": "2.0.0-rc.47",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -189,7 +189,7 @@ export default function SearchModal({
189
189
  {selectedTerms.map((term, index) => (
190
190
  <div
191
191
  key={index}
192
- className="flex items-center gap-1 rounded-full border border-blue-200 bg-blue-100 px-3 py-1 text-sm font-medium text-blue-800"
192
+ className="flex items-center gap-1 rounded-full border border-blue-200 bg-blue-100 px-3 py-1 text-sm font-bold text-blue-800"
193
193
  >
194
194
  <span>{term}</span>
195
195
  <button
@@ -208,7 +208,10 @@ export default function SearchModal({
208
208
  <div className="relative w-full px-6 py-2">
209
209
  {showCompletion && (
210
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}
211
+ <span style={{ visibility: 'hidden' }}>{query}</span>
212
+ {bestCompletion
213
+ .slice(query.length)
214
+ .replace(/ /g, '\u00A0')}
212
215
  </div>
213
216
  )}
214
217
  <input
@@ -282,7 +285,7 @@ export default function SearchModal({
282
285
  {/* Suggestion Pills */}
283
286
  {showSuggestions && (
284
287
  <div className="w-full p-6">
285
- <p className="text-mydarkgrey mb-4 text-sm font-medium">
288
+ <p className="text-mydarkgrey mb-4 text-sm font-bold">
286
289
  Suggestions ({suggestions.length})
287
290
  </p>
288
291
  <div className="flex flex-wrap gap-2">
@@ -290,7 +293,7 @@ export default function SearchModal({
290
293
  <button
291
294
  key={index}
292
295
  onClick={() => handleSuggestionClick(suggestion)}
293
- className={`inline-flex items-center rounded-full border px-3 py-1.5 text-sm font-medium transition-all hover:shadow-md ${getTypeColor(suggestion.type)}`}
296
+ className={`inline-flex items-center rounded-full border px-3 py-1.5 text-sm font-bold transition-all hover:shadow-md ${getTypeColor(suggestion.type)}`}
294
297
  >
295
298
  <span>{suggestion.term}</span>
296
299
  </button>
@@ -23,7 +23,7 @@ interface UseSearchReturn {
23
23
  clearAll: () => void;
24
24
  }
25
25
 
26
- const DEBOUNCE_MS = 100;
26
+ const DEBOUNCE_MS = 150;
27
27
  const BACKEND_THROTTLE_MS = 1200;
28
28
 
29
29
  export function useSearch(): UseSearchReturn {
@@ -39,11 +39,10 @@ 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
43
- const debounceRef = useRef<NodeJS.Timeout>();
44
- const lastSearchTimeRef = useRef<number>(0);
42
+ // --- REVISED STATE FOR SEARCH LOGIC ---
43
+ const searchTimerRef = useRef<NodeJS.Timeout>();
44
+ const lastExecutionTimeRef = useRef<number>(0);
45
45
  const pendingQueryRef = useRef<string | null>(null);
46
- const throttleTimeoutRef = useRef<NodeJS.Timeout>();
47
46
  const inflightQueryRef = useRef<string | null>(null);
48
47
  const api = useMemo(() => new TractStackAPI(), []);
49
48
 
@@ -54,21 +53,17 @@ export function useSearch(): UseSearchReturn {
54
53
  return;
55
54
  }
56
55
 
57
- // Check if this exact query is already inflight
58
56
  if (inflightQueryRef.current === query.trim()) {
59
57
  return;
60
58
  }
61
59
 
62
- // Mark this query as inflight
63
60
  inflightQueryRef.current = query.trim();
64
-
65
61
  setIsDiscovering(true);
66
62
  setDiscoverError(null);
67
63
 
68
64
  try {
69
65
  const response = await api.discover(query.trim());
70
66
 
71
- // Only process response if this is still the current inflight query
72
67
  if (inflightQueryRef.current === query.trim()) {
73
68
  if (response.success && response.data) {
74
69
  setSuggestions(response.data.suggestions);
@@ -78,7 +73,6 @@ export function useSearch(): UseSearchReturn {
78
73
  }
79
74
  }
80
75
  } catch (err) {
81
- // Only process error if this is still the current inflight query
82
76
  if (inflightQueryRef.current === query.trim()) {
83
77
  setDiscoverError(
84
78
  err instanceof Error ? err.message : 'Discovery failed'
@@ -86,7 +80,6 @@ export function useSearch(): UseSearchReturn {
86
80
  setSuggestions([]);
87
81
  }
88
82
  } finally {
89
- // Clear inflight tracking only if this is still the current query
90
83
  if (inflightQueryRef.current === query.trim()) {
91
84
  inflightQueryRef.current = null;
92
85
  setIsDiscovering(false);
@@ -122,28 +115,11 @@ export function useSearch(): UseSearchReturn {
122
115
  [api]
123
116
  );
124
117
 
125
- const executePendingSearch = useCallback(() => {
126
- if (pendingQueryRef.current) {
127
- const queryToExecute = pendingQueryRef.current;
128
- pendingQueryRef.current = null;
129
-
130
- // Update timestamp immediately when scheduling request, not when executing
131
- lastSearchTimeRef.current = Date.now();
132
-
133
- performDiscovery(queryToExecute);
134
- }
135
- }, [performDiscovery]);
136
-
137
118
  const discoverTerms = useCallback(
138
119
  (query: string) => {
139
- // Clear existing debounce timer
140
- if (debounceRef.current) {
141
- clearTimeout(debounceRef.current);
142
- }
143
-
144
- // Clear existing throttle timer
145
- if (throttleTimeoutRef.current) {
146
- clearTimeout(throttleTimeoutRef.current);
120
+ // Clear any existing timer.
121
+ if (searchTimerRef.current) {
122
+ clearTimeout(searchTimerRef.current);
147
123
  }
148
124
 
149
125
  // Clear results when starting new discovery
@@ -160,28 +136,33 @@ export function useSearch(): UseSearchReturn {
160
136
  return;
161
137
  }
162
138
 
163
- // Always store the latest query
139
+ // Always store the latest query for the next execution
164
140
  pendingQueryRef.current = query;
165
141
 
166
- // Debounce first - wait for user to stop typing
167
- debounceRef.current = setTimeout(() => {
168
- const now = Date.now();
169
- const timeSinceLastSearch = now - lastSearchTimeRef.current;
142
+ const now = Date.now();
143
+ const timeSinceLastSearch = now - lastExecutionTimeRef.current;
170
144
 
171
- // If enough time has passed since last search, execute immediately
172
- if (timeSinceLastSearch >= BACKEND_THROTTLE_MS) {
173
- executePendingSearch();
174
- } else {
175
- // Need to wait for throttle - schedule execution
176
- const remainingTime = BACKEND_THROTTLE_MS - timeSinceLastSearch;
177
- throttleTimeoutRef.current = setTimeout(
178
- executePendingSearch,
179
- remainingTime
180
- );
145
+ // Start with the basic debounce delay
146
+ let delay = DEBOUNCE_MS;
147
+
148
+ // If we are inside the throttle window, we must wait longer
149
+ if (timeSinceLastSearch < BACKEND_THROTTLE_MS) {
150
+ const remainingThrottle = BACKEND_THROTTLE_MS - timeSinceLastSearch;
151
+ delay = Math.max(delay, remainingThrottle);
152
+ }
153
+
154
+ searchTimerRef.current = setTimeout(() => {
155
+ // Double check there's a query to run
156
+ if (pendingQueryRef.current !== null) {
157
+ const queryToExecute = pendingQueryRef.current;
158
+
159
+ // Update execution time as soon as the search is initiated
160
+ lastExecutionTimeRef.current = Date.now();
161
+ performDiscovery(queryToExecute);
181
162
  }
182
- }, DEBOUNCE_MS);
163
+ }, delay);
183
164
  },
184
- [executePendingSearch]
165
+ [performDiscovery]
185
166
  );
186
167
 
187
168
  const selectSuggestion = useCallback(
@@ -215,15 +196,12 @@ export function useSearch(): UseSearchReturn {
215
196
  );
216
197
 
217
198
  const clearAll = useCallback(() => {
218
- // Clear all timeouts
219
- if (debounceRef.current) {
220
- clearTimeout(debounceRef.current);
221
- }
222
- if (throttleTimeoutRef.current) {
223
- clearTimeout(throttleTimeoutRef.current);
199
+ // Clear the main search timer
200
+ if (searchTimerRef.current) {
201
+ clearTimeout(searchTimerRef.current);
224
202
  }
225
203
 
226
- // Reset all state including race condition protection
204
+ // Reset all state
227
205
  setSuggestions([]);
228
206
  setIsDiscovering(false);
229
207
  setDiscoverError(null);
@@ -232,6 +210,7 @@ export function useSearch(): UseSearchReturn {
232
210
  setRetrieveError(null);
233
211
  pendingQueryRef.current = null;
234
212
  inflightQueryRef.current = null;
213
+ lastExecutionTimeRef.current = 0; // Reset throttle timer
235
214
  }, []);
236
215
 
237
216
  return {