| 1 | <!-- English translation of adr/0112-command-palette-query-modes-strategy.md. Canonical Russian: ../../adr/0112-command-palette-query-modes-strategy.md --> |
| 2 | |
| 3 | # ADR 0112: Command palette query modes (`f:` / `t:` / `m:` / `x:` / `c:`) — mode model, strategies, and workspace search **backends** |
| 4 | |
| 5 | **Status:** Accepted · Implemented |
| 6 | **Date:** 2026-05-11 · updated 2026-05-12 |
| 7 | |
| 8 | ## Related ADRs |
| 9 | |
| 10 | | ADR | Role | |
| 11 | |-----|------| |
| 12 | | [0013](0013-command-surface-and-discoverability.md) | palette, keyboard-first | |
| 13 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | `IdeCommands`, palette catalog | |
| 14 | | [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md) | Command Melody `c:` and CascadeChord — orthogonal to palette **line** prefixes | |
| 15 | | [0070](0070-command-palette-direct-overlay-surface.md) | palette as overlay | |
| 16 | | [0079](0079-ide-display-system-ids-overlay-pipeline.md) | IDS, overlay hints | |
| 17 | | [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | parametric tail after alias in `c:` | |
| 18 | | [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | melody catalog | |
| 19 | | [0105](0105-hybrid-codebase-index-for-csharp-web.md) | Hybrid Codebase Index (core + MCP) for C# stacks with Roslyn truth | |
| 20 | | [0106](0106-hybrid-codebase-index-cascadeide-integration-and-semantic-map.md) | HCI integration in CascadeIDE | |
| 21 | |
| 22 | ### Outside ADR |
| 23 | |
| 24 | | Document | Role | |
| 25 | |----------|------| |
| 26 | | [intent-melody-language-v1.md](../../intent-melody-language-v1.md) | Intent Melody Language v1 | |
| 27 | |
| 28 | **Code touchpoints (actual baseline):** `IdeCommandPaletteFilterOrchestrator`, `IntentMelodyAliases.TryGetTail`, `GoToAllQueryParser`, `CommandPaletteChromeProjection`, `IdeCommandPaletteExecutionOrchestrator`. |
| 29 | |
| 30 | ### Implementation snapshot |
| 31 | |
| 32 | | Item | Value | |
| 33 | |------|-------| |
| 34 | | — | stages 1–4: mode parse model, chrome, rg/hci/auto backends, TOML | |
| 35 | | — | per-mode strategies — see `CommandPaletteParsedQueryParser` and orchestrator branches | |
| 36 | |
| 37 | ## Summary |
| 38 | |
| 39 | - Palette (**Ctrl+Q**): query line modes (`t:`/`m:`/`x:`), backend strategies. |
| 40 | - Workspace search contract and switching in `settings.toml`. |
| 41 | - Direct overlay surface — [0070](0070-command-palette-direct-overlay-surface.md). |
| 42 | |
| 43 | --- |
| 44 | |
| 45 | ## Context |
| 46 | |
| 47 | Palette hotkey (default **Ctrl+Q**) opens **one query line** and **one result list**, but input semantics are **not uniform**: |
| 48 | |
| 49 | | Prefix | Working name | Query meaning | Where defined today | |
| 50 | |--------|--------------|---------------|---------------------| |
| 51 | | *(none)* | command catalog | fuzzy on title/`command_id` | orchestrator default branch | |
| 52 | | `f:` | go to file | filter solution files | `GoToAllQueryParser` + `RefreshGoToPaletteFilter` | |
| 53 | | `t:` | go to type | search types in `.cs` | same | |
| 54 | | `m:` | go to member | heuristic member search in `.cs` | same | |
| 55 | | `x:` | text | ripgrep over workspace | same | |
| 56 | | `c:` | Command Melody | alias → `command_id`, parametric `:start:end` | `IntentMelodyAliases.TryGetTail` + `RefreshMelodyPaletteFilter` | |
| 57 | |
| 58 | Footer/placeholder hints duplicate this set in text (**`CommandPaletteChromeProjection`**), while **mode branching** sits in **`if` chain at start of `RefreshCommandPaletteFilter`**: first `c:`, then `f|t|m|x`, else catalog. |
| 59 | |
| 60 | **Problem:** mode is an **unnamed concept in code**; check order, async go-to cancel (`goToHandle.Cancel()`), row result types, and UI quirks (`IdeCommandPaletteRowViewModel`: melody vs catalog vs go-to) are spread without one “palette mode” contract. |
| 61 | |
| 62 | Risks on growth: new prefix or priority change (e.g. future `w:`) touches many places; mode tests are unstructured. |
| 63 | |
| 64 | --- |
| 65 | |
| 66 | ## Decision |
| 67 | |
| 68 | Introduce two aligned layers: |
| 69 | |
| 70 | 1. **Query line mode** (what user typed) — **`CommandPaletteQueryMode`** / **`CommandPaletteParsedQuery`** (§1–2). |
| 71 | 2. **Workspace search backend** for `t:` / `m:` / `x:` submodes — **separate contract** with pluggable implementations and switch config (§7). Not a UI mode: same prefix can use different engines without changing palette syntax. |
| 72 | |
| 73 | Mode handling as **strategy per mode** (Strategy pattern / small registry — implementation detail not fixed here). Go-to strategy for `t:`/`m:`/`x:` **delegates** to chosen backend (or fallback chain), not direct `rg` calls from orchestrator. |
| 74 | |
| 75 | <a id="adr0112-p1"></a> |
| 76 | |
| 77 | ### 1. Model |
| 78 | |
| 79 | - **`CommandPaletteParsedQuery`** (or equivalent): discriminated union: |
| 80 | - **Melody** — normalized tail after `c:` (as `TryGetTail` today); |
| 81 | - **GoTo** — `GoToAllQuery` (prefix `f` | `t` | `m` | `x` + term); |
| 82 | - **Catalog** — string without reserved mode prefix (trim + fuzzy on catalog). |
| 83 | |
| 84 | Document **parse priority** (compatibility with current behavior): |
| 85 | |
| 86 | 1. `c:` — first (so `c:` does not collide with go-to and catalog); |
| 87 | 2. then `f:` / `t:` / `m:` / `x:`; |
| 88 | 3. else catalog. |
| 89 | |
| 90 | <a id="adr0112-p2"></a> |
| 91 | |
| 92 | ### 2. Strategy per mode |
| 93 | |
| 94 | Per mode — **one entry**: filter context (solution, roots, file path, editor text, hotkeys, UI mode family, …) + **action**: |
| 95 | |
| 96 | - clear/fill `filteredEntries`; |
| 97 | - set selected index; |
| 98 | - call `refreshCommandPaletteSurfaceSnapshot`; |
| 99 | - for go-to — manage **cancellation** of background search (`CommandPaletteGoToAsyncHandle`), as today. |
| 100 | |
| 101 | **Refactoring goal:** `RefreshCommandPaletteFilter` becomes **dispatcher**: `parse → lookup strategy → execute`, without duplicating “who cancels go-to” rules. |
| 102 | |
| 103 | <a id="adr0112-p3"></a> |
| 104 | |
| 105 | ### 3. Link to execution (Enter) |
| 106 | |
| 107 | **Execution** of selection is partly unified via `IdeCommandPaletteRowViewModel.RowKind` and `IdeCommandPaletteExecutionOrchestrator` (MCP command vs go-to navigation). Filter mode and RowKind must stay **aligned**; introducing strategies must not change user-facing row contract without separate ADR. |
| 108 | |
| 109 | <a id="adr0112-p4"></a> |
| 110 | |
| 111 | ### 4. Chrome and discoverability |
| 112 | |
| 113 | **`CommandPaletteChromeProjection`** texts (footer / placeholder) should **build from canonical mode list** (prefix name + short label) so new mode is added **in one data place**, not by copying `"f: file · …"`. |
| 114 | |
| 115 | <a id="adr0112-p5"></a> |
| 116 | |
| 117 | ### 5. Testability |
| 118 | |
| 119 | - Unit tests on **parse** raw string → mode + payload (edge cases: `c:` without tail, `x:` only spaces; no expected `c:` vs catalog conflict in v1). |
| 120 | - Where possible — tests on **strategy order** (regression: `c:something` does not fall through to catalog as plain text without product decision). |
| 121 | |
| 122 | <a id="adr0112-p6"></a> |
| 123 | |
| 124 | ### 6. Go-to backend: ripgrep vs **HCI** (Hybrid Codebase Index) |
| 125 | |
| 126 | Using **`SearchHybridAsync`** / index instead of **`RipgrepWorkspaceSearchService`** for `t:` / `m:` / `x:` is **not automatically** “faster and better” for all modes — depends on **mode goal** and **index freshness**. |
| 127 | |
| 128 | | Prefix | Today (baseline) | HCI as candidate | Nuances | |
| 129 | |--------|------------------|------------------|---------| |
| 130 | | `f:` | solution tree, in-memory path filter | usually **not needed** | Bottleneck is not disk search; index little help. | |
| 131 | | `x:` | literal via ripgrep over workspace | **often fits**: FTS on indexed chunks | Plus: no `rg` process, predictable delay with **warm** index. Minus: until reindex — **desync** with disk; FTS semantics (tokens, AND) ≠ “like rg”; literal editor-like search may need **rg fallback**. | |
| 132 | | `t:` / `m:` | **regex on `.cs`** (`GoToPaletteRipgrepPatternBuilder`) | **debatable without hit model work** | HCI indexes **text fragments**, not necessarily same quality as Roslyn/VS “Go to Type/Member”. Extra/missed lines vs current regex heuristic. Target accuracy for “type/member” closer to **Roslyn** (separate track) than “just FTS”. | |
| 133 | | (optional) | — | HCI **`semantic`** | Good for **similar meaning**, not strict symbol-name contracts — do not replace `t:`/`m:` without explicit product mode. | |
| 134 | |
| 135 | Contract details and switching — §7. |
| 136 | |
| 137 | <a id="adr0112-p7"></a> |
| 138 | |
| 139 | ### 7. First-class workspace search **backend** (`t:` / `m:` / `x:`) |
| 140 | |
| 141 | **Boundary:** “backend” applies to **async search in file contents under workspace root** for **`t:`**, **`m:`**, **`x:`**. Mode **`f:`** (path filter on solution tree) **not in this contract** — separate synchronous strategy without “rg-like” engine swap. |
| 142 | |
| 143 | **Contract (working name):** abstraction like **`ICommandPaletteGoToSearchBackend`** with unified in/out: |
| 144 | |
| 145 | - **In:** normalized `GoToAllQuery`, workspace root, limits (`MaxRipgrepMatches` / equivalent), index scope (aligned with [HCI scope](0106-hybrid-codebase-index-cascadeide-integration-and-semantic-map.md)), `CancellationToken`. |
| 146 | - **Out:** ordered list of **canonical palette hits** (path, line, column, short label/category — compatible with `IdeCommandPaletteRowViewModel` / existing `CommandPaletteGoTo*NavRowsProjection`). |
| 147 | |
| 148 | **Implementations (minimum set):** |
| 149 | |
| 150 | | Implementation | Role | |
| 151 | |----------------|------| |
| 152 | | **Ripgrep** | Current behavior: `GoToPaletteRipgrepPatternBuilder` + `RipgrepWorkspaceSearchService`. | |
| 153 | | **HCI-FTS** | Query Hybrid Index (semantic optional), map hit → same DTO. | |
| 154 | | **Composite / Auto** | Chain: e.g. HCI → on empty, error, or “index not ready” — **fallback** to Ripgrep. Policy explicit, not hidden in orchestrator. | |
| 155 | |
| 156 | **Switch configuration** — only in user **`settings.toml`** (`%LocalAppData%\CascadeIDE\`, [0028](0028-user-settings-toml-localappdata-and-secrets.md)): same tree as `CascadeIdeSettings` / `SettingsService.Load`, **not** repo `.cascade/workspace.toml` (`UiWorkspaceToml` — zones, navigation presets, etc.). “How palette searches” is **workstation preference**, not per-repo artifact. |
| 157 | |
| 158 | **Schema placement (aligned with existing keys):** |
| 159 | |
| 160 | | Option | Verdict | |
| 161 | |--------|---------| |
| 162 | | **`[hybrid_index]`** | **Do not** use for `backend`: with value **`rg`** setting is not about index; mixes HCI enablement and independent palette engine choice. `auto` ↔ HCI link — only in facade **code**. | |
| 163 | | **`[workspace]`** | **Do not** use: in code `WorkspaceSettings` — panels, `Flight`, splitters; unrelated to search backend. | |
| 164 | | **`[command_palette…]`** | **Yes**: new top-level property on `CascadeIdeSettings`, nested TOML table like `[display.screens.grammar]` — snake_case keys. | |
| 165 | |
| 166 | Implementation mapping (guide): `CascadeIdeSettings.CommandPalette.GoToSearch.Backend` → **`[command_palette.go_to_search]`**, field **`backend`**. |
| 167 | |
| 168 | Canonical backend values: |
| 169 | |
| 170 | | Value | Meaning | |
| 171 | |-------|---------| |
| 172 | | **`rg`** | **Ripgrep only** (current baseline; recommended **default** until HCI path stable). | |
| 173 | | **`hci`** | **Hybrid Codebase Index only** (FTS hit → palette rows). | |
| 174 | | **`auto`** | HCI first; on index not ready, error, or empty response of relevant kind — **fallback** to `rg`. Fallback policy in Composite facade code, not scattered in UI. | |
| 175 | |
| 176 | Example (same user file as `[hybrid_index]`): |
| 177 | |
| 178 | ```toml |
| 179 | [command_palette.go_to_search] |
| 180 | backend = "rg" # "rg" | "hci" | "auto" |
| 181 | ``` |
| 182 | |
| 183 | - **Per-prefix overrides** (`x:` ≠ `t:`) — optional **second iteration** if v1 can ship without them. |
| 184 | |
| 185 | **Support and testing:** |
| 186 | |
| 187 | - New backend = **one interface implementation** + DI/factory registration; orchestrator does not branch `if (useHci)`. |
| 188 | - Orchestrator unit tests on **fake backend**; separate tests per implementation (hit ↔ row mapping). |
| 189 | |
| 190 | **UX (optional):** short source label in subtitle (“rg” / “index”) or diagnostics only — product choice; user need not see backend name by default. |
| 191 | |
| 192 | --- |
| 193 | |
| 194 | ## Rejected / deferred alternatives |
| 195 | |
| 196 | - **Plugin prefixes from TOML in v1** — heavier protocol and validation; defer until second prefix source besides code. |
| 197 | - **Merge `c:` and go-to into one grammar** — breaks IML and VS-style go-to mental model. |
| 198 | - **Single regex parser for entire line** — possible implementation detail; architecturally **mode type and strategy** matter. |
| 199 | |
| 200 | --- |
| 201 | |
| 202 | ## Consequences |
| 203 | |
| 204 | - **Positive:** one explicit entry for mode docs; easier prefix and side effects (go-to cancel); less chrome/code drift; **engine switch** (`rg` ↔ HCI ↔ chain) without orchestrator `if` explosion. |
| 205 | - **Negative:** refactor scope — orchestrator, DI, settings, tests; after interface extraction user behavior in v1 should stay **parity** with Ripgrep backend by default (change only via setting — intentional). |
| 206 | |
| 207 | --- |
| 208 | |
| 209 | ## Implementation status |
| 210 | |
| 211 | Implemented in app and tests: `CommandPaletteParsedQuery` / `CommandPaletteParsedQueryParser`, `ICommandPaletteGoToSearchBackend` (+ `rg` / HCI / `auto`), `[command_palette.go_to_search]` in settings, canonical hints `CommandPaletteChromeModeHints`. |
| 212 | |
| 213 | Further improvements (plugin prefixes, extended `x:` semantics) — open directions per this ADR, not blockers for current scope. |
| 214 | |