Search in the toolbar

Part 1 proposes a search for each lens. Part 2 merges them into one field that finds people, quotes, transcript lines, tags, themes, signals and sessions. It searches the current project, or every project in the current folder, and lists the results in a single view.

Artefact: what the product showsCommentary: why
Palette
Scheme

What exists today. Search only works in the Quotes lens. The SPA filters in the browser: a case-insensitive substring match from 3 characters, with a 150 ms debounce (utils/filter.ts). It checks the quote text, speaker name, tag names and sentiment. Matches are highlighted with .search-mark. On the Mac the field is a native capsule (QuotesToolbarControls.swift) and ⌘F focuses it. Sessions, Codebook and Signals show a disabled SearchComingSoonButton, and the Project lens shows no search control at all.

No search runs on the server. No route takes a q parameter, and nothing uses FTS5 or embeddings. MCP search_quotes loads every quote and runs the same substring match in Python. Stale claim: design-multi-project.md §3b says FTS5 is "already available in the per-project schema". It is not.

Part 1 — One search per lens

The per-lens searches come in two different shapes. A filter narrows a set: rows that don't match disappear. A find moves a cursor through a document: nothing disappears, and you step from hit to hit. Four lenses are filters and the transcript is a find. That is why ⌘G was withdrawn from the Quotes lens (a filter has no cursor to step), and why it comes back in the transcript.

Quotes · filter · shipped, plus a count

IKEA checkout studyQuotes · 23 of 214
delivery23

“I got all the way to the end and then the delivery was £45, which is more than the shelf.”

12:04p2 Tomfrustrationhidden costs

“Click and collect or delivery? I don’t know which one is cheaper until I’ve picked.”

08:51p1 Amiraconfusion

Keep it as it is, and add two things. (1) Put the match count inside the capsule and in the window subtitle (“23 of 214”). The vanilla report used to show a count, and the SPA dropped it. (2) Allow token prefixes in the same field, p3, #trust and is:starred, so the keyboard can reach the tag sidebar's job. Both Mail and Finder offer the same thing as a suggestion menu. Part 2 builds that menu and makes it the shared mechanism, so this lens needs no separate syntax.

Session transcript · find, not filter · the step recorded as next

IKEA checkout studys2 · Tom · 41 min
delivery3 of 7
11:40m1And what did you expect to happen next?
11:52p2I thought it’d tell me the delivery price on the product page, honestly.
12:04p2I got all the way to the end and then the delivery was £45, which is more than the shelf.
12:19m1Mm. What would you do at that point?

Every hit is highlighted and one of them is the current hit (outlined). ⌘G and ⇧⌘G step forward and back, and the transcript scrolls to keep the current hit in view. This brings back the Find Next and Find Previous menu items that are commented out at MenuCommands.swift:751-791. It is also the condition the private notes set for restoring them.

When the playhead follows the current hit, a researcher can hear every mention of a word in about a minute. No other lens can do that. It is also the highest-value item on record (menu review, Finding 20). It needs no server work: the transcript is already loaded, so this is a DOM walk plus a cursor.

Sessions · filter rows + count hits in transcripts

IKEA checkout studySessions · 5 of 8
delivery
SessionParticipantMentions
s2Tom7
s1Amira4
s5Jo2
s3Priya—

This matches participant names, speaker codes, session ids, filenames and dates, and it also counts mentions in each transcript. Clicking a row opens that transcript with Find already running on the same word, so the list and the transcript chain together.

Rows with no match are dimmed rather than hidden, so the session numbering stays readable. The counts need either every transcript loaded in the client or the server endpoint from Part 3.

Codebook · filter tags by name + definition

IKEA checkout studyCodebook · 3 tags
Library cost
UXR · Friction
hidden costs14 quotes
price surprise — “…total cost revealed late”6
Nielsen · Visibility
system status — “…cost of an action”2

This searches tag names first and then their definitions. A match inside a definition is shown as a short snippet, so you can see why the row matched. Groups with no matching tag disappear, because in this lens hiding is what a filter is for. The same field also filters the Library sheet (design-codebook-v2.md:254: the manager needs search more than the catalogue does).

Signals · filter cards, dim the heatmap

IKEA checkout studySignals · 2 of 11
delivery

Checkout — delivery options

frustration · 9 quotes · 4 people

Hidden costs

doubt · 6 quotes · 3 people · delivery in 5

A card matches when its section or theme label matches, or when the quotes behind it contain the word. The card then says how many of its quotes do (“delivery in 5”). In the heatmap, cells that don't match are dimmed rather than removed, so the grid keeps its shape. Signals are computed when the page is requested, so this filters their labels and the quote ids behind them. Nothing new needs to be stored.

Project · no local search: the field is the global one

IKEA checkout studyProject
Search project
Sessions
8
Quotes
214
Themes
11

The dashboard has too little on it to filter. Its field is therefore the global search from Part 2 (placeholder “Search project”) rather than a disabled button. It is also where a researcher who doesn't yet know which lens holds the answer will start.

Summary of Part 1.

LensShapeMatchesNon-match⌘GNeeds server?
Quotesfiltertext, edits, speaker, tags, sentimenthidden—no (shipped)
Transcriptfindsegment textkeptnext / prevno
Sessionsfilter + countspeople, codes, ids, filenames, dates; transcript mentionsdimmed—for counts
Codebookfiltertag and group names, definitionshidden—no
Signalsfiltersection and theme labels, quotes behind cardsdimmed (grid)—no
Projectglobal entryeverything (Part 2)——yes

All six use one native capsule, extended beyond Quotes. The bridge message would change from setSearchQuery to setSearchQuery {lens, text}. The canSearch gate would open on every lens except Welcome, which stays dim by decision (Finding 19). Search is still not restored between app launches (LensMemory.swift:25).

Part 2 — One field that finds everything

The merge keeps the per-lens field and adds suggestions as you type, in the way Mail does. Pressing ↩ does what it does today and filters the lens you are in. Choosing a suggestion turns the field's contents into a token (a person, a tag, a theme) or opens that item. ⌘↩ opens every result, grouped by type, in a single results view.

State

after typing “deliv” in the Quotes lens

IKEA checkout studyQuotes
deliv Person p2 TomTag hidden costs4 delivery
Quotes containing “deliv”23
People
p2 Tom · says “deliv…” most7
Tags
delivery time5 quotes
Sections & themes
Checkout — delivery optionssection · 9
Signals
Delivery options × frustration4 people
Transcripts
41 lines in 6 sessionss2 has 7
Show all results for “deliv”⌘↩
↩ filter Quotes↑↓ chooseesc close

“The kitchen planner kept losing my measurements whenever I went back a step.”

04:18p3 Priyafrustration

“Click and collect or delivery? I don’t know which one is cheaper until I’ve picked.”

08:51p1 Amiraconfusion

“I liked that it showed the stock at my store straight away.”

10:02p5 Josatisfaction

“I got all the way to the end and then the delivery was £45, which is more than the shelf.”

12:04p2 Tomfrustrationhidden costs

“The fitting kit was extra too. Every screen found another tenner.”

19:33p2 Tomdoubthidden costs
Search: This project IKEA (folder · 3 projects) 77 results 112 results in 3 projects
Results
All77112
Quotes2349
Transcripts4138
People37
Tags11
Sections & themes13
Signals24
Sessions610
People3
p2 Tom7p1 Amira4p5 Jo2

“I got all the way to the end and then the delivery was £45…”

12:04p2 TomfrustrationCheckout — delivery options

“Click and collect or delivery? I don’t know which one is cheaper until I’ve picked.”

08:51p1 Amiraconfusion
Transcripts41 lines · 6 sessionsShow all →
s2 11:52p2I thought it’d tell me the delivery price on the product page, honestly.
s5 03:10p5Is delivery next day or is that only for the small stuff?
Tags · Sections · Signals4
delivery time § Checkout — delivery options ▦ Delivery options × frustration ▦ Hidden costs × doubt
Quotes49 in 3 projectsShow all →
IKEA checkout study

“I got all the way to the end and then the delivery was £45…”

12:04p2frustration
IKEA bookcase finder

“It fits, great — but will they deliver up four flights?”

22:40p4doubt
IKEA returns pilot

“Collecting it back costs the same as the delivery did.”

05:12p1frustration
People7 speakers in 3 projects
checkoutp27bookcasep45returnsp13+4
Tagsin 2 of 3 projects
delivery timecheckout 5 · returns 3
1 project on an unmounted volume was not searched: IKEA kitchen planner — Samsung T7

This is the pattern Mail and Finder already use. The first row, highlighted, is always the free-text match for the lens you are in, so pressing ↩ keeps today's behaviour exactly. Below it, one suggestion per type (people, tags, sections, signals, transcripts), with a count on every row so the menu also answers “how much is there?”. The menu is capped at about 8 rows, and the last one opens all results.

Keys. This follows Mail. ⌘F searches what you are looking at: Find in a transcript, the filter in any other lens. ⌥⌘F searches the whole project, as Mail's Mailbox Search does. ⌥⌘F currently belongs to Focus Mode, which gives it back and needs a new shortcut; ⌃⌘F is the system's Enter Full Screen, so it is not available. / keeps working on the web.

Choosing a person or a tag turns it into a token, the blue pill that Mail uses. Tokens combine with AND: here, p2 and the tag hidden costs. The token for a tag does the same job as the tag sidebar, and the two stay in sync: the sidebar shows the tag ticked. Backspace removes the last token.

Open decision: should tokens stay applied when you switch lens? My recommendation is yes, within a session. The query describes what you are looking at, and the lens is only how you view it. So p2 in Quotes becomes p2's sessions in Sessions and p2's cells in Signals. The query is still not restored after a relaunch.

The results view is a pseudo-lens. It uses the same window and the same sidebar geometry as the other lenses, and the sidebar's rows become result types, the way Mail's sidebar becomes mailboxes. Each group shows its first few rows, and “Show all in Quotes →” carries the query into the real lens, where it becomes a filter. The scope bar is Finder's (“Search: This Mac | ‘Folder’”).

In folder scope, the rules from design-multi-project.md §3c apply to how results are shown. The project name is the first thing on every row, above the quote and in bold, not in fine print (Finding 5). People appear as speaker codes only, because a participant who consented to one study may not have consented to being linked across studies. Showing names would need an explicit opt-in with a warning. Projects on unmounted volumes are named, not silently skipped (§3b constraints).

Part 3 — What you're deciding, and how it's built

Scope: you asked for all projects, but the design record says the folder is the boundary. design-multi-project.md §3b says: “Global — Not supported.” §3c Finding 1 says the folder is the confidentiality boundary between competing clients, “period”. The threat it names is a freelancer who shares their screen, types “onboarding”, and shows client A's participants to client B. The mockup therefore offers This project and this folder. Adding “All projects” later is one more item on the scope bar, and the provenance rules above already assume results come from more than one project. Your options:

  1. Keep folder scope (recommended). This follows the record and does not reopen it.
  2. Allow all projects, but only as an explicit choice. It is never the default and is not remembered after a relaunch, and people always appear as codes. If you pick this, it overturns §3b and needs a line in that doc.
  3. Allow all projects when there are no folders. A researcher with one client and loose projects has no boundary to protect.

Build order. Each step ships on its own.

  1. Find in the transcript with ⌘G. Client-side only. This is the highest-value step, and it is the condition for restoring the withdrawn menu items.
  2. Turn on the capsule in Sessions, Codebook and Signals. Client-side filters over data that is already loaded. Retire SearchComingSoonButton. The bridge message gains a lens field.
  3. Add a server search endpoint: GET /api/projects/{id}/search?q=&kinds=&limit=, returning a count per type plus the top N hits of each. Back it with an FTS5 table per project covering quote text and edits, transcript segments, people, tag names and definitions, and heading edits. Build the table after stage 12 and update it on edit. Signals are computed at request time, so the endpoint runs load_signals and matches labels and quote ids. MCP search_quotes should call the same core, following the grounding rule of one shared core and never a parallel sibling.
  4. Suggestions menu and results pseudo-lens for one project. The UI above.
  5. Folder scope, once the multi-project work in §7 is in place. The Mac already runs one sidecar per project (ServeFleet), so the natural fan-out is for each project's own sidecar to answer for its own database. That may also avoid the question of whether one sidecar can read another project's folder under the sandbox. Unverified: that last point needs measuring, not assuming.

New pieces this adds (the no-bespoke rule means they are flagged, not just drawn): the suggestions popover, search tokens in the field, the results pseudo-lens and its scope bar, the provenance pill, and the transcript's current-hit outline. Everything else reuses shipped parts: .search-mark, .badge with group tints, the quote card, the sidebar row, and the native capsule.

Native note: the capsule is a SwiftUI TextField, not an NSSearchField. Tokens (NSSearchField's token support, .searchable(tokens:)) are the one feature that argues for revisiting the choice left open in §4.4 of design-desktop-nav-toolbar-rearrangement.md. The popover can be a SwiftUI popover either way.