Skip to content

Home search finds every saved meeting, including by speaker name - #1806

Merged
r3dbars merged 4 commits into
mainfrom
claude/project-thread-gfqgk2
Sep 24, 2026
Merged

r3dbars merged 4 commits into
mainfrom
claude/project-thread-gfqgk2

Conversation

@r3dbars

@r3dbars r3dbars commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Requested by Justin · project thread

Before: searching on the Meetings page only looked at the ~10 meetings already on screen, and only at their title and date. Searching for last month's meeting or a person's name said "No meetings match your search. Older meetings load with Show more." (the button is actually "Load more", and each click adds only 10).

After: search looks through every saved meeting, by title, date, or speaker name. Results show newest first, 50 at a time, with "Load more" for the rest. While the first pass runs on a big library, the list says "Searching all meetings…" instead of "no matches".

Why

Paper cuts audit item 2 (paper-cuts/audit.md): people search for an old meeting or a person and get told nothing matches.

Product Impact

  • Affects: meetings
  • Lane: activation
  • Why this matters: finding an old meeting is a core reason to keep transcripts around; today search silently misses almost everything.

What changed

  • RecentMeetingsScanner.loadSearchIndex lists every saved meeting. Each row comes from, in order: the previous in-memory index (same file stamp), one bulk read of the SQLite metadata cache (RecentMeetingMetadataCache.allRows()), or a transcript parse that then fills the cache. No audio probes; audio is resolved only for the matches shown.
  • Speaker names come from the transcript's speaker labels ([System/Alex], [System/[[Alex]]]), dropping the channel prefix, "You", and generic labels ("Speaker 2", "Unknown speaker", "Review later"). They're stored in the Home metadata cache. speakerNames is required on decode, so older cache rows miss once and get reparsed (same pattern as the system-audio verification field).
  • New HomeMeetingSearchIndex (Foundation-pure) holds the rows plus a prepared haystack per row, reuses unchanged entries on rebuild, and pages matches. Matching reuses HomeMeetingListFilter so semantics are unchanged (every token, case/diacritic-insensitive).
  • HomeViewModel debounces typing (150 ms), runs the search off-main with supersession guards, and rebuilds the index when Home refreshes or captures change on disk. Deleting a meeting drops it from results right away.
  • Copy: "Find meetings" (field and toolbar help), "Find Meetings…" (⌘F menu), "No meetings match your search.", "Searching all meetings…".
  • Benchmark: HomeRecentCaptureBenchmark now also times the search at 1k and 10k captures (cold index, index from SQLite cache, and warm rebuild+search), checks it finds the oldest meeting, and holds warm rebuild+search to the existing load budget. Uses a private cache file so fixture rows never land in the real cache.

How: the search is a separate in-memory index next to the existing paged Home list, so opening Home stays exactly as fast as before (it still loads only 10). The index is built the first time someone types and then kept warm.

How I checked it

  • scripts/dev/agent-preflight.sh
  • python3 scripts/dev/check-build-source-lists.py, check-duplicate-declarations.py, bash -n on touched scripts
  • bash build.sh --no-open (no Swift toolchain in this session; CI builds it)
  • bash run-tests.sh (CI) — new Tests/HomeMeetingSearchIndexTests.swift covers name extraction, the cache-miss-on-old-payload rule, paging, removal, finding a meeting outside the loaded slice by speaker name, and serving rows from cache/previous index without reading the transcript
  • Performance budget (CI app-build job runs the Home recent-captures benchmark)
  • Manual check: type a name from an old meeting on the Meetings page

Risk Review

  • Privacy / local-first behavior reviewed — nothing new leaves the Mac; no analytics payload changes (reuses load_more_meetings)
  • Storage path or migration impact reviewed — same cache file; old rows reparse once lazily
  • Public-facing copy stays concrete and matches current product scope
  • Release/update impact reviewed — none
  • No private transcripts, audio, tokens, personal paths, or customer data are included

Notes

The Swift here has not been compiled locally; CI is the first build.

🤖 Generated with Claude Code

https://claude.ai/code/session_017s1xcNJjmfpSS9sjesJfHN


Generated by Claude Code

The Meetings search box only filtered the ~10 meetings already loaded on
screen, and matched title and date only. It now searches every saved
meeting's title, date, and named speakers.

- RecentMeetingsScanner.loadSearchIndex lists every meeting, reusing the
  previous in-memory index, then one bulk read of the SQLite metadata
  cache, and only parses transcripts on a miss. Audio is resolved only for
  the matches shown.
- Speaker names are pulled from the transcript labels (no "You", no
  "Speaker 2") and cached; older cache rows miss once and are reparsed.
- HomeViewModel debounces typing, pages results 50 at a time, and
  rebuilds the index when captures change.
- Copy: "Find meetings", "Find Meetings…", "No meetings match your
  search." (the old text pointed at a "Show more" button that doesn't
  exist).
- The Home recent-captures benchmark now also times the search at 1k and
  10k captures, with warm rebuild+search held to the load budget.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017s1xcNJjmfpSS9sjesJfHN
@r3dbars r3dbars self-assigned this Sep 24, 2026
Review fixes for the Home meetings search:
- Typing no longer clears the current results, so the list doesn't flash
  back to the loaded slice on every pause. The view still re-applies the
  current query to them.
- Search haystacks carry "Today"/"Yesterday", so the index records the
  day it was built and rebuilds haystacks when the day changes.
- A warm rebuild that misses only a few rows looks them up one by one
  instead of decoding the whole cache table, and allRows() decodes after
  releasing the cache lock.
- RecentMeetingItem.withAudio copies self, so new fields can't drop out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017s1xcNJjmfpSS9sjesJfHN
- "Remote" / "Remote Participant" placeholder labels no longer count as
  speaker names, so searching "remote" doesn't match every meeting.
- The first full-library index build writes parsed rows to the cache in
  batched transactions instead of one fsync per row.
- A new query resets the old query's Load more state.
- A background refresh of the recent slice no longer hides search results
  behind the loading spinner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017s1xcNJjmfpSS9sjesJfHN
@r3dbars
r3dbars marked this pull request as ready for review September 24, 2026 03:00
@r3dbars
r3dbars merged commit 7225725 into main Sep 24, 2026
7 checks passed
@r3dbars
r3dbars deleted the claude/project-thread-gfqgk2 branch September 24, 2026 03:00
claude Bot pushed a commit that referenced this pull request Sep 24, 2026
Keeps the cancel-returns-now LoadTaskBox loader and uses #1806's
scanCandidates in loadRecent, with the post-listing cancel check moved
into scanCandidates so the search index build stops there too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QEji7nWg5mSyPSXzn3eb85
@claude claude Bot mentioned this pull request Sep 24, 2026
9 of 11 tasks
claude Bot pushed a commit that referenced this pull request Sep 24, 2026
…st Mic + call audio

Resolved per release/merge-notes/1796-vs-1787-1794.md: keep both sides in
the controller; #1794's unverified line plus the unheard-playback copy;
the recovered notice's OK-only display before #1794's Check Access
display; the hidden-primary rule alongside #1794's new buttons.

Check Access now covers "can't hear the call" while macOS doesn't say
access is on (a tap without access hears only zeros), and not the
recovered notice. Tests added.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgXMcLRGBtiSiybdgyD1VJ
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.

2 participants