/** Scope is captured with filters; changing controls cannot mutate active work. */ export type SearchFilters = Readonly< Record >; export function scopedFilters( scope: string, filters: Record, ): SearchFilters { if (scope !== "all" && scope !== "code") throw new Error("Choose a valid search scope."); return Object.freeze({ ...filters, code_only: scope === "code" }); } /** Let rapid typing settle before starting the model lane. */ export const SEMANTIC_SETTLE_MS = 120; /** A rebuild retains the previous committed generation. Backend readiness is * withdrawn only while a replacement query graph is being prepared. */ export function semanticAvailable( status: { ready: boolean; generation: number | null; coverage?: string | null; } | null, ) { return Boolean( status?.ready && status.generation !== null && status.coverage !== null, ); } /** One active request per lane; intermediate keystrokes collapse to the latest. * Native work already running cannot be cancelled, so revisions guard every * result and retry. Busy responses receive at most one delayed retry. */ export class LatestLane { private pending: T | undefined; private running = false; private run: (value: T) => Promise; private current: (value: T) => boolean; private fail: (value: T, error: unknown) => void; private delay: () => Promise; constructor( run: (value: T) => Promise, current: (value: T) => boolean, fail: (value: T, error: unknown) => void, delay = () => new Promise((resolve) => setTimeout(resolve, 400)), ) { this.run = run; this.current = current; this.fail = fail; this.delay = delay; } submit(value: T) { this.pending = value; if (!this.running) void this.drain(); } clear() { this.pending = undefined; } private async drain() { this.running = true; try { while (this.pending !== undefined) { const value = this.pending; this.pending = undefined; if (!this.current(value)) continue; for (let attempt = 0; attempt < 2; attempt++) { try { await this.run(value); break; } catch (error) { if (!this.current(value)) break; if ( attempt === 0 && /busy|in progress|currently running|already running/i.test( String(error), ) ) { await this.delay(); if (!this.current(value) || this.pending !== undefined) break; } else { this.fail(value, error); break; } } } } } finally { this.running = false; } } } /** An unsuccessful automatic build is not retried on every status poll. */ export class AutomaticIndexGate { private attempted = new Set(); claim(key: string) { if (this.attempted.has(key)) return false; this.attempted.add(key); // Bound memory even across a very long desktop session. if (this.attempted.size > 128) this.attempted.delete(this.attempted.values().next().value!); return true; } } export type SemanticPhase = | "preparing" | "indexing" | "pending" | "unavailable" | "no-content" | "complete"; /** Zero keyword matches are provisional until the semantic lane completes. */ export function emptySearchState( lane: "lexical" | "hybrid", phase: SemanticPhase, ) { const title = lane === "lexical" ? "No keyword matches yet." : "No matches in the current index yet."; if (phase === "preparing") return { title: lane === "lexical" ? title : "No matches in the current index yet.", detail: "Google semantic search is still preparing. This query will also search by meaning when it is ready.", }; if (phase === "indexing") return { title: lane === "lexical" ? title : "No matches in the current index yet.", detail: "Semantic indexing is underway. Results may change when your documents finish indexing.", }; if (phase === "pending") return { title, detail: "Searching by meaning… Semantic results will appear here when this query finishes.", }; if (phase === "unavailable") return { title, detail: "Semantic results are currently unavailable; see the model status above. Try a filename or words from your document, or retry the search.", }; if (phase === "no-content") return { title, detail: "No document content is indexed for semantic search yet. Index a folder with readable documents, or search a filename.", }; return { title: lane === "hybrid" ? "No matches for this search." : title, detail: "Try another phrase, broaden your filters, or index another folder.", }; }