Drawer search: loading feedback + relevance ordering everywhere#33
Merged
Conversation
Every drawer-side search now gives the same kind of feedback while embeddings are being computed and ranks results by closest match. Chat already did this; the wiki, journal, and recipe sidebars now behave the same way. Wiki: the sidebar used to override the server-returned similarity order with an alphabetical sort and only showed a "Loading wiki..." string on the very first fetch. Subsequent searches read as frozen. The sort now switches to alphabetical only when the query is empty; an active query keeps the cosine-ordered result list intact and a Scanner replaces the listing for the duration of any in-flight search. Journal: the sidebar was substring-only over the eagerly-loaded `journal.entries` list with no spinner. It now embeds the query via Venice and calls supabase.searchJournalEntries (the same function the assistant's journal_search tool uses), aggregates matching entries by date keeping the best per-date similarity as the date's rank, and shows the Scanner while the round-trip is in flight. Empty-query browse mode is unchanged. Recipes: the cookbook originally had no embedding pipeline at all on the rationale that ILIKE-on-title is enough for a single-user cookbook. That's true for the LLM tool path, but the drawer is a human surface where "fluffy potato side" should find "Mashed Potatoes". Added the standard column + claim/save/search RPC trio to public.recipes, a clear_recipe_embedding_on_change trigger, the sources/recipes.ts worker adapter, and a searchRecipes() method on SupabaseService that merges semantic and ILIKE hits the same way searchWikiArticles does. The recipe_list / recipe_search tools still use the ILIKE path; the new pipeline only feeds the sidebar. Chat: the search box already ranked exact title hits ahead of semantic ones and ordered semantic hits by cosine similarity. The Scanner only appeared on a first-result-empty search though, so subsequent queries kept showing stale results until they returned. Aligned with the other three so the spinner replaces any prior result list whenever a new search is in flight. Docs: fleshed out docs/user/search.md (was a stub) so the new behaviour is documented per-tab, and updated docs/dev/cookbook.md and docs/dev/embeddings.md to mention the recipes source.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SYNOPSIS
Every drawer-side search now shows a Scanner spinner while
the embed+query round-trip is in flight and ranks results by
closest match. Chat, wiki, journal, recipes - one shape.
PURPOSE
Currently the four drawer searches disagree on feedback and
order:
Bad because the user can't tell working from frozen between
keystrokes, and the closest-meaning match doesn't surface.
DESCRIPTION
Three layers.
(1) How it works today.
embed→searchThreads(exact-title hits thensemantic by similarity); the Scanner is gated on
searchResults.length === 0so subsequent searches read as frozen.embed→searchWikiArticles(semantic mergedwith ILIKE) but re-sorts the result list alphabetically at the
view layer, and the loading text only fires on first mount.
journal.entriesin-memory by substringover content/mood/topics. No async, no spinner.
cookbook.recipesin-memory by titlesubstring. Schema has no embedding column on
recipes(thecomment in
schema.sql:889says "personal cookbook is small, ILIKEis fast enough").
(2) What this PR changes (same names, same order):
searchBusyis true, not only when results are empty. Orderinguntouched.
active query passes the server's cosine order through. Scanner
replaces the listing for every in-flight search, not just the
initial load.
searchJournalEntries(the samefunction the
journal_searchtool uses). Per-entry hits areaggregated by date keeping the best per-date similarity as
the date's rank. Scanner during in-flight; empty-query browse
mode unchanged.
(no prior infra existed). New columns on
public.recipes(
embedding vector(2048)+ claim cols),clear_recipe_embedding_on_changetrigger, three RPCs (
claim_next_pending_recipe,save_recipe_embedding_if_claimed,search_recipes_by_embedding),one new worker source (
src/lib/embeddings/sources/recipes.ts)registered in
worker.ts, one new service method(
SupabaseService.searchRecipes) that merges semantic+ILIKE thesame way
searchWikiArticlesdoes. Sidebar consumes it. Sortpicker hides during a search since relevance is the active
sort then.
recipe_list/recipe_searchtools areuntouched - still ILIKE-on-title; the new pipeline only feeds
the human drawer search.
(3) How this fixes PURPOSE.
Every search surface now uses the same idiom: type → debounce →
spinner replaces list → ranked results arrive. The closest-meaning
match is at the top in all four tabs.
Notes:
searchRecipesfalls back to ILIKE-only when the new RPC isn'tpresent yet, matching the wiki/journal "no Venice client" path.
add column if not exists/drop ... if exists/create or replace) somise run syncis safe to re-run.recipesis updated rather than deleted - the rationale for the original
decision is still relevant context (the LLM tool path still uses
ILIKE; the new pipeline is for the human drawer).
matching is replaced with the server-side semantic+ILIKE merge,
which is a behavioural change (a query that matched a topic
substring but not content might rank differently now). The
trade-off was accepted as part of choosing the "Add semantic
search everywhere" option.
docs/user/search.mdwas a stub (H1 + placeholders); nowfleshed out per-tab.
docs/dev/cookbook.mdanddocs/dev/embeddings.mdupdated to mention the recipes source.
Test plan
typing and results appear in relevance order
alphabetical; recipes by 'updated' or 'rating'; journal newest-day
first; chat by Recent/Older/Archived buckets)
mise run syncapplies the recipe-embedding schema cleanlynext sweep
mise run checkgreenGenerated by Claude Code