Skip to content

Unified global search & command palette (closes #16) - #31

Merged
sim186 merged 1 commit into
mainfrom
claude/github-command-palette-1kop40
Aug 7, 2026
Merged

Unified global search & command palette (closes #16)#31
sim186 merged 1 commit into
mainfrom
claude/github-command-palette-1kop40

Conversation

@sim186

@sim186 sim186 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Closes #16.

The issue asked for a Spotlight over the whole library. Two things were in the way: search could not see the half of the library the LLM wrote, and the box it was typed into could search but not act.

Search now reads everything

Summaries, action items and translations all live in analysis.result_text, which nothing indexed — so the words a user is most likely to remember reading were the words search could not find. Migration 9 adds a trigger-maintained FTS5 index over it, and a new backend/core/search.py answers a query from five places at once:

kind where the match was found
transcript the spoken words — FTS5 over transcript.full_text
summary LLM output — FTS5 over analysis.result_text
title the file name, or the alias it was renamed to
tag the name of a tag on the recording
folder the name of the folder holding it

A recording that matches in several places is still one result: it carries its strongest match as the snippet and reports every place in matched_in. Ranking is by kind in that order — a better place always outranks a worse one, and matching in several lifts a result only within its own band. The route moved out of folders_tags.py into api/routes/search.py; the response keeps its old keys, so the TUI is unaffected.

The header box became a command palette

Ctrl/ + K opens it from anywhere, including from inside a text field. It searches as you type and runs any command in the app. A leading character narrows it, using the prefixes the TUI palette already uses:

prefix shows
/ commands only
@ recordings only
# folders and tags only

Commands, folders and tags are matched in the browser and appear instantly; recordings come from /api/search, debounced. tui/fuzzy.py's scorer is ported to JS so both palettes rank the same way.

Three defects the work turned up

  • A recording renamed with an alias could not be found by that name. Search read the file name only — the one name the user had chosen was the one it ignored.
  • Snippets went into innerHTML unescaped. They carry <mark> around the match and are otherwise raw transcript text, so a transcript discussing a script tag was one. The palette escapes the snippet whole and restores only those two tags.
  • offset was applied per source and then merged, so paging skipped rows and repeated others. It now walks the merged list.

Behaviour change

Typing in the header no longer live-filters the library list as you type. That now happens on the palette's last row ("Show every result for … in the library") or on Enter. The live filter and the results dropdown were two overlapping UIs on one box, and only one of them could be the hub.

Verification

  • Full suite: 877 passed; ruff --select F clean.
  • New tests: tests/test_search_unified.py (13 — the five sources, one-row-per-recording, ranking between kinds, offset paging, trigger upkeep when an analysis is filled in later or deleted) and tests/test_frontend_command_palette.py (6 — every element id the palette reaches for exists in the markup, the documented prefixes are the implemented ones, the escape step is still there).
  • Driven in a real browser against a seeded library: keyboard open/navigate/activate, opening a recording, running a command, jumping to a tag, the empty state, and mobile width. No console errors.

Two rough edges the browser pass caught and fixed before this landed: fuzzy command matches were burying library hits ("anna" is a subsequence of "Ask your library"), so mixed-mode commands now need a substring match; and ranked commands repeated their section headers, so rows are grouped by section before rendering.

Docs updated: docs/doc.md (new API section), README.md, docs/ROADMAP.md, CHANGELOG.md.

Issue #16 asked for a Spotlight over the whole library. Two things were in
the way: search could not see the half of the library the LLM wrote, and the
box it was typed into could search but not act.

Summaries, action items and translations all live in analysis.result_text,
which nothing indexed — so the words a user is most likely to remember
reading were the words search could not find. A new FTS5 index covers them,
and core/search.py answers a query from five places at once: transcripts,
LLM output, names and aliases, tags, folders. A recording that matches in
several is still one result, carrying the strongest match as its snippet and
naming every place it was found. The route moves out of folders_tags.py,
which was never where the app's central hub belonged.

The header search box is now a command palette on Ctrl/Cmd+K, reachable
mid-sentence from inside any text field. It searches as you type and runs any
command in the app; / @ # narrow it to commands, recordings, or folders and
tags — the prefixes the TUI palette already uses, with its fuzzy matcher
ported so both rank the same way.

Three smaller things the work turned up:

- A recording renamed with an alias could not be found by that name. Search
  read the file name only — the one name the user had chosen was the one it
  ignored.
- The old dropdown put snippets straight into innerHTML. They carry <mark>
  around the match and are otherwise raw transcript text, so a transcript
  discussing a script tag was one.
- offset was applied per source and then merged, so paging skipped rows and
  repeated others. It now walks the merged list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gRFov5dsnRfubA6Rv8cX4
@sim186
sim186 merged commit 64d348d into main Aug 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unified Global Smart Search

2 participants