feat: keep global search useful during partial failures (#199)
CI / lint (pull_request) Successful in 15s
CI / build-frontend (pull_request) Successful in 4s

This commit is contained in:
timmy committed 2026-08-07 14:18:38 +00:00
1 parent 2243b9453c
commit f1448e6b58
6 files changed
+195 -29

No files matched your search

+12 -2
View File
@@ -21,6 +21,7 @@
const delay = options.delay === undefined ? 250 : options.delay;
let timer = null;
let generation = 0;
let activeController = null;
return {
setQuery(value) {
@@ -28,17 +29,26 @@
generation += 1;
const current = generation;
if (timer !== null) clearTimeout(timer);
if (activeController !== null) activeController.abort();
activeController = null;
if (query.length < 2) {
onState({ status: 'idle', query, items: [] });
return;
}
onState({ status: 'loading', query, items: [] });
timer = setTimeout(async () => {
const requestController = new AbortController();
activeController = requestController;
try {
const items = await search(query);
if (current === generation) onState({ status: 'ready', query, items });
const result = await search(query, requestController.signal);
const items = Array.isArray(result) ? result : result.items;
const partial = !Array.isArray(result) && result.partial === true;
if (current === generation) onState({ status: 'ready', query, items, partial });
} catch (error) {
if (error && error.name === 'AbortError') return;
if (current === generation) onState({ status: 'error', query, items: [], error });
} finally {
if (activeController === requestController) activeController = null;
}
}, delay);
},
+7 -2
View File
@@ -1794,13 +1794,17 @@ textarea { resize: vertical; min-height: 120px; }
let commandSearchState = { status:'idle', query:'', items:[] };
let commandItems = [];
let commandSelection = -1;
async function searchGlobalWork(query) {
async function searchGlobalWork(query, signal) {
const response = await fetch('api/v1/search?q=' + encodeURIComponent(query) + '&limit=10', {
headers: { Accept:'application/json' },
signal,
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(payload.error || 'Search is temporarily unavailable.');
return Array.isArray(payload.items) ? payload.items : [];
return {
items: Array.isArray(payload.items) ? payload.items : [],
partial: payload.partial === true,
};
}
const commandSearch = filterCommands.createGlobalSearchController({
search: searchGlobalWork,
@@ -1920,6 +1924,7 @@ textarea { resize: vertical; min-height: 120px; }
}).join('');
if (commandSearchState.status === 'loading') html += '<div class="cmd-status">Searching accessible work…</div>';
else if (commandSearchState.status === 'error') html += '<div class="cmd-status">Search unavailable. Keep typing or retry.</div>';
else if (commandSearchState.partial) html += '<div class="cmd-status">Some results are temporarily unavailable.</div>';
else if (String(filter || '').trim().length >= 2 && !remote.length) html += '<div class="cmd-status">No matching issues or pull requests.</div>';
el.innerHTML = html;
el.querySelectorAll('.cmd-item').forEach((item) => {