From 4138fe593ddbb1637d95ea09374bf775af53b02a Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Sun, 29 Mar 2026 16:45:25 -0400 Subject: [PATCH] feat: instant local search results while full pipeline runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added SearchLocal() — queries only the FTS5 index with no network calls. The frontend now calls SearchLocal first, renders those results immediately (clearing the loading spinner), then fires the full Search() pipeline in the background. When full results arrive, they replace the local hits seamlessly. For indexed queries this means sub-100ms first results regardless of how slow MB/LB are. Unindexed queries still show the loading spinner until the full pipeline completes. --- backend/explore/explore.go | 17 +++++++++++++ .../components/explore-view/explore-view.ts | 25 ++++++++++++++++++- frontend/wailsjs/go/explore/Service.d.ts | 2 ++ frontend/wailsjs/go/explore/Service.js | 4 +++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/backend/explore/explore.go b/backend/explore/explore.go index f085e67..f84c95f 100644 --- a/backend/explore/explore.go +++ b/backend/explore/explore.go @@ -112,6 +112,23 @@ func (e *Service) SearchRecordings(query string) ([]MBRecording, error) { return e.mb.SearchRecordings(e.ctx, query, mbSearchLimit) } +// SearchLocal queries only the local FTS5 index and returns results +// instantly with no network calls. Returns nil if the index isn't +// ready. The frontend calls this in parallel with Search() to show +// instant results while the full pipeline runs. +func (e *Service) SearchLocal(query string) *MBSearchResult { + indexHits := e.index.Search(query, 30) //nolint:mnd + if len(indexHits) == 0 { + return nil + } + + var result MBSearchResult + mergeIndexHits(&result, indexHits) + filterAndCap(&result) + + return &result +} + // --------------------------------------------------------------------------- // MusicBrainz lookup // --------------------------------------------------------------------------- diff --git a/frontend/src/components/explore-view/explore-view.ts b/frontend/src/components/explore-view/explore-view.ts index 571cda8..037b3bd 100644 --- a/frontend/src/components/explore-view/explore-view.ts +++ b/frontend/src/components/explore-view/explore-view.ts @@ -1,7 +1,7 @@ import { LitElement, html, css, nothing } from 'lit'; import { customElement, state, query as litQuery } from 'lit/decorators.js'; import { designTokens } from '../../styles/tokens.css'; -import { Search, GetThumbnails, GetArtistImageURL, CheckLibraryMBIDs } from '@go/explore/Service'; +import { Search, SearchLocal, GetThumbnails, GetArtistImageURL, CheckLibraryMBIDs } from '@go/explore/Service'; import type { ThumbnailRequest } from '@go/explore/Service'; import type { MBSearchResult, @@ -520,6 +520,29 @@ export class ExploreView extends LitElement { const startTime = performance.now(); console.log(`[explore] search started: "${query}"`); + // Phase 1: show local index hits instantly (no network). + try { + const local = await SearchLocal(query); + if (version !== this.searchVersion) return; + if (local && (local.artists?.length || local.releaseGroups?.length || local.recordings?.length)) { + this.results = local; + this.loading = false; + this.loadThumbnails(); + this.loadArtistImages(); + this.checkLibrary(); + const elapsed = (performance.now() - startTime).toFixed(0); + console.log( + `[explore] local results: "${query}" in ${elapsed}ms — ` + + `artists=${local.artists?.length ?? 0}, ` + + `albums=${local.releaseGroups?.length ?? 0}, ` + + `tracks=${local.recordings?.length ?? 0}`, + ); + } + } catch { + // Local search failed — continue to full search. + } + + // Phase 2: full pipeline (MB + LB + reranking). try { const result = await Search(query); diff --git a/frontend/wailsjs/go/explore/Service.d.ts b/frontend/wailsjs/go/explore/Service.d.ts index 1ba3e4e..028dc17 100755 --- a/frontend/wailsjs/go/explore/Service.d.ts +++ b/frontend/wailsjs/go/explore/Service.d.ts @@ -35,6 +35,8 @@ export function Search(arg1:string):Promise; export function SearchArtists(arg1:string):Promise>; +export function SearchLocal(arg1:string):Promise; + export function SearchRecordings(arg1:string):Promise>; export function SearchReleaseGroups(arg1:string):Promise>; diff --git a/frontend/wailsjs/go/explore/Service.js b/frontend/wailsjs/go/explore/Service.js index 217a451..f1a1f46 100755 --- a/frontend/wailsjs/go/explore/Service.js +++ b/frontend/wailsjs/go/explore/Service.js @@ -66,6 +66,10 @@ export function SearchArtists(arg1) { return window['go']['explore']['Service']['SearchArtists'](arg1); } +export function SearchLocal(arg1) { + return window['go']['explore']['Service']['SearchLocal'](arg1); +} + export function SearchRecordings(arg1) { return window['go']['explore']['Service']['SearchRecordings'](arg1); }