@@ -11,40 +11,47 @@ chip row and path-pill column landing in later milestones.
1111
1212## Files
1313
14- | File | Purpose |
15- | ---------------------------------- | -------------------------------------------------------------------------------------------- |
16- | ` SearchDialog.svelte ` | Orchestrator: overlay, mount/unmount, keyboard dispatch, search execution, state wiring |
17- | ` SearchBar.svelte ` | Unified query input: one ` <input> ` for AI / filename / regex, placeholder updates per mode |
18- | ` SearchModeChips.svelte ` | Mode chip row below the bar: AI / Filename / Content (disabled) / Regex, arrow-key navigable |
19- | ` SearchFilterChips.svelte ` | Filter chip strip (Size, Modified, Search in) plus Add filter dropdown. Each opens a popover |
20- | ` FilterChip.svelte ` | Single chip: default/configured states, ` × ` clear, Backspace clear, aria-expanded |
21- | ` FilterChipPopover.svelte ` | Generic popover: frosted-glass, auto-flip, focus trap, Esc closes without disrupting dialog |
22- | ` filter-chip-state.ts ` | Pure helpers: ` deriveSizeChip ` , ` deriveDateChip ` , ` deriveScopeChip ` (testable in isolation) |
23- | ` SearchResults.svelte ` | Column headers + results list + all states (loading, empty, populated) + status bar |
24- | ` search-state.svelte.ts ` | Module-level ` $state ` for query fields, results, index readiness, AI state |
25- | ` search-state.test.ts ` | Vitest tests for state helpers (` parseSizeToBytes ` , ` buildSearchQuery ` , etc.) |
26- | ` filter-chip-state.test.ts ` | Default → configured → cleared rules for each filter chip's display summary |
27- | ` SearchBar.svelte.test.ts ` | Per-mode placeholder, value mirror, ` onInput ` callback |
28- | ` SearchModeChips.svelte.test.ts ` | Chip set, active marker, click + keyboard activation, focus motion (skipping Content) |
29- | ` SearchFilterChips.svelte.test.ts ` | Chip rendering, ` × ` and Backspace clear, popover open/close, Add filter list, scope behavior |
30- | ` SearchDialog.svelte.test.ts ` | ` ⌘N ` clears, close+reopen preserves, ` ⌘1 ` /` ⌘2 ` /` ⌘3 ` mode switch, ` ⌘Enter ` triggers AI |
31- | ` SearchDialog.a11y.test.ts ` | Tier-3 axe-core audit across loading / index-ready / AI-on macro-states |
32- | ` SearchFilterChips.a11y.test.ts ` | Tier-3 axe-core audit across default, configured, disabled, and open-popover states |
33- | ` SearchResults.a11y.test.ts ` | Tier-3 axe-core audit across result states |
34-
35- ## State shape (post-M2)
14+ | File | Purpose |
15+ | ------------------------------------ | --------------------------------------------------------------------------------------------------------- |
16+ | ` SearchDialog.svelte ` | Orchestrator: overlay, mount/unmount, keyboard dispatch, search execution, state wiring |
17+ | ` SearchBar.svelte ` | Unified query input: one ` <input> ` for AI / filename / regex, placeholder updates per mode |
18+ | ` SearchModeChips.svelte ` | Mode chip row below the bar: AI / Filename / Content (disabled) / Regex, arrow-key navigable |
19+ | ` AiTransparencyStrip.svelte ` | Strip below the chip row showing the original AI prompt, the caveat, and a disabled Refine button |
20+ | ` SearchFilterChips.svelte ` | Filter chip strip (Size, Modified, Search in) plus Add filter dropdown. Each opens a popover |
21+ | ` FilterChip.svelte ` | Single chip: default/configured states, ` × ` clear, Backspace clear, aria-expanded |
22+ | ` FilterChipPopover.svelte ` | Generic popover: frosted-glass, auto-flip, focus trap, Esc closes without disrupting dialog |
23+ | ` filter-chip-state.ts ` | Pure helpers: ` deriveSizeChip ` , ` deriveDateChip ` , ` deriveScopeChip ` (testable in isolation) |
24+ | ` SearchResults.svelte ` | Column headers + results list + all states (loading, empty, populated) + status bar |
25+ | ` search-state.svelte.ts ` | Module-level ` $state ` for query fields, results, index readiness, AI state |
26+ | ` search-state.test.ts ` | Vitest tests for state helpers (` parseSizeToBytes ` , ` buildSearchQuery ` , etc.) |
27+ | ` filter-chip-state.test.ts ` | Default → configured → cleared rules for each filter chip's display summary |
28+ | ` SearchBar.svelte.test.ts ` | Per-mode placeholder, value mirror, ` onInput ` callback |
29+ | ` SearchModeChips.svelte.test.ts ` | Chip set, active marker, click + keyboard activation, focus motion (skipping Content) |
30+ | ` SearchFilterChips.svelte.test.ts ` | Chip rendering, ` × ` and Backspace clear, popover open/close, Add filter list, scope behavior |
31+ | ` AiTransparencyStrip.svelte.test.ts ` | Renders prompt, renders caveat when set, Refine button is disabled with Coming soon tooltip |
32+ | ` SearchDialog.svelte.test.ts ` | ` ⌘N ` clears, close+reopen preserves, ` ⌘1 ` /` ⌘2 ` /` ⌘3 ` mode switch, ` ⌘Enter ` triggers AI, AI strip lifecycle |
33+ | ` SearchDialog.a11y.test.ts ` | Tier-3 axe-core audit across loading / index-ready / AI-on macro-states |
34+ | ` SearchFilterChips.a11y.test.ts ` | Tier-3 axe-core audit across default, configured, disabled, and open-popover states |
35+ | ` AiTransparencyStrip.a11y.test.ts ` | Tier-3 axe-core audit for prompt-only and prompt-plus-caveat states |
36+ | ` SearchResults.a11y.test.ts ` | Tier-3 axe-core audit across result states |
37+
38+ ## State shape (post-M4)
3639
3740The user's typed text and the active mode are one model:
3841
3942``` ts
4043let query = $state (' ' ) // The text in the bar
4144let mode = $state <SearchMode >(' filename' ) // 'ai' | 'filename' | 'regex'
45+ let lastAiPrompt = $state <string | null >(null ) // The natural-language prompt before AI overwrites `query`
46+ let lastAiCaveat = $state <string | null >(null ) // The AI translator's caveat (or null)
4247```
4348
44- ` buildSearchQuery() ` reads both: ` mode === 'regex' ` produces ` patternType: 'regex' ` , anything else produces
45- ` patternType: 'glob' ` . AI mode is only ever invoked via ` executeAiSearch() ` , which calls ` translateSearchQuery ` and then
46- overwrites ` query ` + ` mode ` with the AI's result (so the user sees what was searched). M4 will surface the original
47- prompt in a transparency strip; for M2 it lives only in the user's memory.
49+ ` buildSearchQuery() ` reads ` query ` + ` mode ` : ` mode === 'regex' ` produces ` patternType: 'regex' ` , anything else produces
50+ ` patternType: 'glob' ` . AI mode is only ever invoked via ` executeAiSearch() ` , which (1) captures the user's prompt into
51+ ` lastAiPrompt ` , (2) calls ` translateSearchQuery ` , (3) overwrites ` query ` + ` mode ` with the AI's result so the user can
52+ see and iterate on the translated pattern, and (4) sets ` lastAiCaveat ` from the result. The ` AiTransparencyStrip ` is
53+ visible whenever ` lastAiPrompt ` is non-null; it clears on ` ⌘N ` (via ` clearSearchState ` ) and on any successful non-AI
54+ search (` executeSearch(fromAiTranslation = false) ` ).
4855
4956There is ** no ` aiPrompt ` state and no ` namePattern ` state** . M2 deleted both. Anywhere the old code read ` aiPrompt ` or
5057` namePattern ` , the new code reads ` query ` . Anywhere the old code branched on ` patternType ` , the new code branches on
@@ -120,9 +127,20 @@ a message ("Drive index not ready...") with scan progress if available. Inputs a
120127extracts keywords, Rust builds the query deterministically), then runs ` executeSearch() ` . No preflight, no refinement
121128pass. The previous two-pass system caused ~ 15% regressions; deterministic structure means there's nothing to refine.
122129
123- ** AI overwrites the bar** : After AI translates, the bar shows the AI's translated pattern (filename / regex), and ` mode `
124- flips accordingly. The user sees what was searched and can keep iterating. The original natural-language prompt is
125- preserved only in the user's memory until M4 ships the transparency strip.
130+ ** AI overwrites the bar; the strip preserves the prompt** : After AI translates, the bar shows the AI's translated
131+ pattern (filename / regex), and ` mode ` flips accordingly. The user sees what was searched and can keep iterating. The
132+ original natural-language prompt and the AI's caveat are surfaced in the ` AiTransparencyStrip ` below the chip row. The
133+ strip is the source of truth for "what did I ask the AI?" once the bar has been overwritten. Lifecycle:
134+
135+ - ` executeAiSearch(trimmed) ` sets ` lastAiPrompt = trimmed ` BEFORE calling ` translateSearchQuery ` . The capture is
136+ unconditional: even if the IPC fails, the user still sees what they asked.
137+ - After the translation succeeds, ` lastAiCaveat = translateResult.caveat ?? null ` .
138+ - ` executeSearch(fromAiTranslation: boolean) ` clears both fields when ` fromAiTranslation ` is false. ` executeAiSearch `
139+ passes ` true ` , so the AI flow's tail (` executeSearch(true) ` ) leaves the strip intact.
140+ - ` clearSearchState() ` (called by ` ⌘N ` ) clears both fields.
141+
142+ The disabled "Refine…" button on the strip is the placeholder for the chat-back UX. No keyboard shortcut is wired (same
143+ contract as the Content mode chip: visible-disabled with an explanatory tooltip is fine; shortcut-but-no-op is hostile).
126144
127145** Auto mode fallback when AI gets disabled mid-session** : If the AI provider is switched off while the dialog is open
128146and the active mode is ` ai ` , the dialog quietly flips to ` filename ` . The user wouldn't be able to run a search
@@ -175,9 +193,9 @@ close + reopen into a lost-work moment. The only sanctioned reset path is `⌘N`
175193state from a lifecycle hook, you probably want a user-initiated action instead.
176194
177195** Gotcha** : The AI's translation overwrites ` query ` and ` mode ` . ** Why** : We want the bar to show what was searched, not
178- the natural-language prompt. Until M4 ships the transparency strip, the original prompt is only in the user's memory.
179- Anyone building on top of this should not assume ` query ` still contains the user's natural-language input after an AI
180- run.
196+ the natural-language prompt. The original prompt is preserved separately in ` lastAiPrompt ` (set by ` executeAiSearch `
197+ before the IPC call) so the ` AiTransparencyStrip ` can render it. Anyone building on top of this should not assume
198+ ` query ` still contains the user's natural-language input after an AI run; use ` getLastAiPrompt() ` instead .
181199
182200## References
183201
0 commit comments