Private
Public Access
0
0

docs(command-palette): rewrite guide to match actual implementation

- Updated to reflect 13 tests (6 unit + 7 live_gui) instead of hypothetical async test
- Removed Everything mode and async context preview sections (not yet implemented; marked as future work)
- Updated Commands Registry section to reference actual src/commands.py file
- Added Implementation section with file layout and Command/CommandRegistry/CommandModal reference
- Added Built-in Commands table reflecting the actual 11 commands shipped
- Added Adding Custom Commands section with decorator and explicit-Command patterns
- Added Keyboard Reference table
- Updated Testing section with accurate coverage and test pattern
- Moved unimplemented features (Everything mode, user-defined commands, plugin system) to Future Work
This commit is contained in:
2026-06-02 22:46:48 -04:00
parent d7449ae417
commit 86d093e101
3 changed files with 293 additions and 352 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ separate_tool_calls_panel = true
bg_shader_enabled = false bg_shader_enabled = false
crt_filter_enabled = false crt_filter_enabled = false
separate_task_dag = false separate_task_dag = false
separate_usage_analytics = true separate_usage_analytics = false
separate_tier1 = false separate_tier1 = false
separate_tier2 = false separate_tier2 = false
separate_tier3 = false separate_tier3 = false
@@ -33,7 +33,7 @@ separate_tier4 = false
separate_external_tools = false separate_external_tools = false
[gui.show_windows] [gui.show_windows]
"Project Settings" = true "Project Settings" = false
"Files & Media" = true "Files & Media" = true
"AI Settings" = true "AI Settings" = true
"MMA Dashboard" = false "MMA Dashboard" = false
+245 -308
View File
@@ -6,422 +6,359 @@
## Overview ## Overview
The **Command Palette** is a global, keyboard-driven launcher for actions across Manual Slop. Triggered by `Ctrl+Shift+P` (or the View menu), it presents a fuzzy-searchable list of every command available in the current context. Selecting a command executes it; the palette closes. The **Command Palette** is a global, keyboard-driven launcher for actions across Manual Slop. Triggered by `Ctrl+Shift+P`, it presents a fuzzy-searchable list of every registered command. The user types a query, navigates with Up/Down arrows, and executes with Enter (or mouse click). Escape closes.
The Command Palette also includes an **"Everything" mode** (Ctrl+Shift+E) that searches across all available actions, including file content, symbols, commands, and history. This is the primary discovery mechanism for new users and the fastest navigation tool for power users.
This guide covers: This guide covers:
1. **Architecture** — Where the Command Palette sits in the input pipeline 1. **Architecture** — Where the Command Palette sits in the render pipeline
2. **Commands Registry** — How commands are registered 2. **Implementation** — File layout and the `Command` data model
3. **Fuzzy Search** — Matching algorithm and ranking 3. **Commands Registry** — How commands are registered
4. **Async Context Preview** — Why context preview is async and what it does 4. **Fuzzy Search** — Matching algorithm and scoring
5. **"Everything" Mode** — Cross-domain search 5. **Built-in Commands**The 11+ commands shipped today
6. **Testing** — Test patterns 6. **Adding Custom Commands** — How to extend the library
7. **Testing** — Unit and integration coverage
8. **Limitations & Future Work**
--- ---
## Architecture ## Architecture
The Command Palette is a modal that sits in the main render loop. It is **synchronous from the user's perspective** (the user types, sees results, picks one) but uses **async I/O** for the underlying context preview to avoid blocking the GUI on long-running file reads. The Command Palette is a modal rendered as part of the main render loop. The user types in a search field; `fuzzy_match` ranks the registered commands; Up/Down arrows navigate; Enter or click executes. The palette is **synchronous** the command count is small enough that there's no need for async context preview.
``` ```
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ Render Loop │ Render Loop (_gui_func in src/gui_2.py)
│ - Detects Ctrl+Shift+P → opens palette modal │ - Detects Ctrl+Shift+P → toggles palette
│ - Modal is rendered at the top of the stack │ - Calls render_palette_modal(self, commands)
│ - User types in the search box │
│ - Fuzzy matcher ranks results in real time │
│ - "Everything" mode triggers async context │
│ preview worker │
└─────────────────┬───────────────────────────────┘ └─────────────────┬───────────────────────────────┘
reads commands from calls
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
Commands Registry render_palette_modal(app, commands)
│ - Built-in commands (file ops, MMA, view, etc.) │ - Sets window focus on first open
│ - User-defined commands (from config) │ - Captures arrow/enter keys BEFORE input_text
│ - Plugin commands (future) │ - Renders centered 600x400 modal
│ - Handles Up/Down/Enter/Escape │
└─────────────────┬───────────────────────────────┘ └─────────────────┬───────────────────────────────┘
fuzzy-matched against reads from
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
Fuzzy Matcher (synchronous) CommandRegistry (in src/command_palette.py)
│ - Returns ranked list of (command, score) │ - registry.all() returns List[Command]
│ - Updates as user types │ - Commands are registered at module import time
│ - Supports fuzzy subsequence matching │
└─────────────────┬───────────────────────────────┘ └─────────────────┬───────────────────────────────┘
for "Everything" mode, queries populated by
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
Async Context Preview Worker src/commands.py — 11+ static commands
│ - Reads file content, symbols, history │ - Decorated with @registry.register
│ - Runs in background thread to avoid frame │ - Each has id, title, category, action
stutter - All actions take app: App as parameter
│ - Results stream into the palette incrementally │
└─────────────────────────────────────────────────┘ └─────────────────────────────────────────────────┘
``` ```
**Why async context preview**: For the "Everything" mode, the palette may need to read file content, search symbols, or query history. These operations can be slow (especially over a large codebase). Running them synchronously in the render thread would cause visible UI hangs. The async worker streams results as they become available. ### Per-Open State
The palette tracks per-open state on the `App` instance:
- `show_command_palette: bool` — visibility flag
- `_command_palette_query: str` — current search text
- `_command_palette_selected: int` — currently selected index
- `_command_palette_focused: bool` — set on first open, for `set_next_window_focus`
- `_command_palette_input_focused: bool` — set on first open, for `set_keyboard_focus_here`
All four are reset to their initial values (`""`, `0`, `False`) when the palette closes.
--- ---
## Commands Registry ## Implementation
### Built-in Commands ### File Layout
Manual Slop ships with ~50 built-in commands, organized by category: | File | Role |
| Category | Examples |
|---|---| |---|---|
| **File** | Open File, Save File, Close File, Revert File | | `src/command_palette.py` | `Command` dataclass, `CommandRegistry`, `fuzzy_match`, `render_palette_modal`, `_close_palette`, `_execute` helpers |
| **Edit** | Find, Replace, Go to Line, Go to Symbol | | `src/commands.py` | Static command definitions (~11+ commands) |
| **View** | Toggle Panel, Switch Theme, Switch Profile | | `src/gui_2.py` | `App.show_command_palette` flag, `_toggle_command_palette` method, Ctrl+Shift+P handler in `_gui_func`, modal render call |
| **AI** | Send to AI, Compress Discussion, Show Comms Log | | `tests/test_command_palette.py` | 6 unit tests |
| **MMA** | Start Track, Pause Engine, Resume Engine, Kill Worker | | `tests/test_command_palette_sim.py` | 7 live_gui integration tests |
| **Tools** | Run PowerShell, Open External Editor, Trigger Hot Reload |
| **Project** | Switch Project, Reload Project, Save Project TOML |
| **Window** | Minimize, Maximize, Close, Reset Layout |
| **Help** | Open Documentation, About, Show Diagnostics |
### Command Schema ### The `Command` Data Model
```python ```python
@dataclass @dataclass
class Command: class Command:
id: str # Unique identifier id: str # Unique identifier
title: str # Display name title: str # Display name (fuzzy-matched)
category: str # Category for grouping category: str # Category label (e.g., "File", "View")
shortcut: Optional[str] # Keyboard shortcut (e.g., "Ctrl+S") shortcut: Optional[str] = None # Reserved for direct shortcuts (not yet rendered)
description: str = "" # Optional help text description: str = "" # Optional help text
enabled_when: Optional[str] = None # Condition for enablement (e.g., "has_selection") enabled_when: Optional[str] = None # Reserved for future conditional enablement
action: Callable # Function to execute when selected action: Optional[Callable] = None # The function to call when selected
``` ```
Commands are registered in `src/commands.py` (or similar) at module import time. The registry exposes: ### The `CommandRegistry`
```python ```python
def get_all_commands() -> List[Command]: class CommandRegistry:
"""Returns all registered commands.""" def __init__(self) -> None:
self._commands: Dict[str, Command] = {}
def get_command_by_id(command_id: str) -> Optional[Command]: def register(self, command_or_callable: Any) -> Any:
"""Returns a command by its ID, or None if not found.""" """Decorator: @registry.register on a function. The function name becomes
the command id; the function's docstring or underscored_name becomes
the title; the action is the function itself."""
if isinstance(command_or_callable, Command):
cmd = command_or_callable
else:
cmd = Command(
id=command_or_callable.__name__,
title=command_or_callable.__name__.replace("_", " ").title(),
category="uncategorized",
action=command_or_callable,
)
if cmd.id in self._commands:
raise ValueError(f"Command {cmd.id} already registered")
self._commands[cmd.id] = cmd
return command_or_callable
def all(self) -> List[Command]:
return list(self._commands.values())
def get(self, command_id: str) -> Optional[Command]:
return self._commands.get(command_id)
``` ```
### User-Defined Commands ### The `render_palette_modal` Function
Users can add custom commands via `config.toml`: Module-level function (per the project's UI delegation pattern — see [guide_architecture.md](guide_architecture.md)). Takes `app: App` as its first parameter; never reaches into App state directly.
```toml ```python
[command_palette.custom_commands.my_format] def render_palette_modal(app: Any, commands: List[Command]) -> None:
title = "Format Selected File" """Renders the Command Palette as a centered modal. Called from _gui_func."""
category = "Custom" if not getattr(app, "show_command_palette", False):
action_type = "shell" return
action = "powershell -File format_script.ps1 {current_file}" # ... sets up window focus on first open ...
shortcut = "Ctrl+Shift+F" # ... processes Up/Down/Enter keys BEFORE input_text ...
# ... calls input_text and draws result list ...
# ... returns early via _close_palette on Escape ...
``` ```
`action_type` is one of: ### The Keyboard Handler
- `"shell"` — Run a shell command (with `{current_file}`, `{current_selection}`, etc. as placeholders)
- `"python"` — Call a Python function (function must be importable) In `App._gui_func`, immediately after the existing Ctrl+Alt+R (hot reload) handler:
- `"palette"` — Open another palette with filtered commands
```python
if (io.key_ctrl and io.key_shift
and not io.key_alt and not io.key_super
and imgui.is_key_pressed(imgui.Key.p)):
self.show_command_palette = not self.show_command_palette
if self.show_command_palette:
# Reset state on open
...
```
--- ---
## Fuzzy Search ## Fuzzy Search
The matching algorithm is a **fuzzy subsequence matcher with scoring**. The matching algorithm is a **fuzzy subsequence matcher with scoring**, implemented as a pure function in `src/command_palette.py`.
### Algorithm ### Algorithm
Given the query string (what the user typed) and a candidate command's title, the matcher: Given the query string and a candidate command's title:
1. **Subsequence check**: Verifies that all characters in the query appear in the title, in order (case-insensitive). This is the "fuzzy" part — the user doesn't need to type the exact command name. 1. **Subsequence check**: All characters in the query must appear in the title, in order, case-insensitively. The "fuzzy" part — the user doesn't need to type the exact command name.
2. **Score calculation** (4 components, summed):
2. **Score calculation**: If subsequence matches, calculates a score based on: - **Exact prefix match** (+1.0): Query is a prefix of the title
- **Exact prefix match** (highest score): Query is a prefix of the title - **Word boundary match** (+0.5): Query starts at a word boundary in the title
- **Word boundary match**: Query starts at a word boundary in the title - **Contiguous match** (+0.3): All matched characters are contiguous
- **Contiguous match**: All matched characters are contiguous in the title - **Gap penalty** (-0.1 per gap): Penalty for non-contiguous matches
- **Character distance**: Smaller gaps between matched characters score higher 3. **Sort by score descending**, return top N (default 20).
3. **Result sorting**: Results are sorted by score (descending). The top N (configurable, default 20) are displayed.
### Example Matches ### Example Matches
Query: `"fld"` (user wants "Find in Selection") Query: `"fld"` against titles:
| Title | Match | Score | | Title | Match | Score |
|---|---|---| |---|---|---|
| "Find in Selection" | subsequence `f-i-n-d in S-e-L-ection` → no | 0 (rejected) | | "Fold All" | exact prefix | 1.0 (prefix) + 0.3 (contiguous) = 1.3 |
| "Fold All" | subsequence `F-o-L-D` → yes | high (exact prefix) | | "File List" | subsequence | 0 (gaps > 0) |
| "File List" | subsequence `F-i-L-e` → yes | medium (gaps) | | "Find in Selection" | rejected | no subsequence match |
The result list shows "Fold All" first, then "File List".
### Performance ### Performance
The matcher is **synchronous** and **fast**: ~1ms for 100 commands on a typical query. The palette re-runs the matcher on every keystroke, so performance is bounded by the number of registered commands (~50) and the query length (typically <20 chars). ~1ms for ~50 commands on a typical query. The palette re-runs the matcher on every keystroke; with the current command count this is imperceptible.
--- ---
## Async Context Preview ## Built-in Commands
When the palette is in "Everything" mode, the underlying search may need to perform I/O: The 11 commands currently shipped in `src/commands.py`:
- **File content search** (text content within tracked files) | ID | Title | Category | Action |
- **Symbol search** (definitions, references via `py_find_usages` etc.) |---|---|---|---|
- **History search** (past AI responses, discussion entries) | `reset_session` | Reset Session | AI | Calls `ai_client.reset_session()` and the App's reset handler |
| `clear_discussion` | Clear Discussion | AI | Empties `app.discussion_history` |
| `toggle_diagnostics` | Toggle Diagnostics | View | Toggles `app.show_diagnostics` |
| `add_all_files_to_context` | Add All Files To Context | AI | Calls `app._add_all_files_to_context()` |
| `open_project` | Open Project | Project | Calls `app._show_project_picker()` |
| `save_project` | Save Project | Project | Calls `app._save_project_state()` |
| `trigger_hot_reload` | Hot Reload | Tools | Calls `HotReloader.reload("src.gui_2", app)` |
| `show_documentation` | Show Documentation | Help | Opens the project URL in the browser |
| `switch_to_dark_theme` | Switch To Dark Theme | View | `theme_2.apply("10x Dark")` |
| `switch_to_light_theme` | Switch To Light Theme | View | `theme_2.apply("ImGui Light")` |
| `switch_to_nerv_theme` | Switch To Nerv Theme | View | `theme_2.apply("NERV")` |
### Why Async ### Defensive Action Calls
A synchronous search over a large codebase (1000+ files) could take seconds. Blocking the render thread for that duration would cause visible UI freezes. The async worker pattern decouples the search from the render: Every command action is wrapped in a defensive `hasattr` check:
```
User types "foo" in Everything mode
├─ Palette starts async search worker
├─ Render loop continues; palette shows "Searching..." spinner
├─ Worker thread: searches file content, symbols, history
├─ Worker emits results as they come (incremental streaming)
├─ Render loop: on each frame, drain pending results and update palette
└─ When worker completes, palette shows final results
```
### Streaming
Results are streamed via a thread-safe queue:
```python ```python
class AsyncSearchWorker: @registry.register
def __init__(self): def clear_discussion(app: "App") -> None:
self._results_queue: queue.Queue = queue.Queue() """Clear Discussion — Clear all entries in the current discussion."""
self._stop_event: threading.Event = threading.Event() if hasattr(app, "discussion_history"):
app.discussion_history = []
def search(self, query: str) -> None:
"""Starts the search in a background thread."""
threading.Thread(target=self._search_impl, args=(query,), daemon=True).start()
def drain_results(self) -> List[SearchResult]:
"""Called by the render loop to drain pending results."""
results = []
while True:
try:
results.append(self._results_queue.get_nowait())
except queue.Empty:
break
return results
``` ```
The render loop calls `drain_results()` once per frame, adding any new results to the palette's display list. The user sees results appear incrementally as the search progresses. This makes commands safe to register even if the corresponding App state hasn't been initialized yet. The `_execute` helper in `render_palette_modal` additionally wraps the action call in a try/except so a buggy action doesn't break the modal's end_child/end pairing (which would cause the `IM_ASSERT: Must call EndChild() and not End()!` crash).
### Cancellation ---
If the user types another character before the previous search completes, the previous search is cancelled: ## Adding Custom Commands
To add a new command, edit `src/commands.py`:
```python ```python
def on_query_changed(self, new_query: str) -> None: @registry.register
if self.current_worker: def my_new_command(app: "App") -> None:
self.current_worker.cancel() # Sets _stop_event """My New Command — Does something useful."""
self.current_worker = AsyncSearchWorker() if hasattr(app, "my_attr"):
self.current_worker.search(new_query) app.my_attr.do_something()
``` ```
The cancelled worker checks `_stop_event` periodically and exits early. Partial results are discarded. The decorator extracts the function's name as the command ID and converts the underscored name to a human-readable title (e.g., `my_new_command` → "My New Command"). If you want a custom title, register an explicit `Command` instance:
```python
registry.register(Command(
id="save_all",
title="Save All Open Files",
category="File",
action=lambda app: app._save_all(),
))
```
### Conventions
- **ID**: snake_case, unique within the registry
- **Title**: Human-readable, used for fuzzy matching
- **Category**: Short label for grouping (currently informational; not yet rendered in the palette)
- **Action**: A function taking `app: App`. Wrap in `try/except` if needed; the palette already wraps in try/except too.
--- ---
## "Everything" Mode ## Keyboard Reference
The "Everything" mode is a special palette mode that searches across multiple domains: | Key | Action |
|---|---|
| `Ctrl+Shift+P` | Toggle the palette |
| `Escape` | Close the palette |
| `Up Arrow` | Move selection up |
| `Down Arrow` | Move selection down |
| `Enter` / `Keypad Enter` | Execute the selected command |
| `Click` on a result | Execute that command |
| Typing in the input | Fuzzy-filter the result list |
| Domain | Source | Filter | The arrow keys are processed BEFORE the input field consumes them, ensuring Up/Down navigate the list and not just the text cursor.
|---|---|---|
| **Commands** | Built-in + user-defined | All |
| **Files** | Tracked files in current project | `f:<query>` or auto-detected |
| **Symbols** | Definitions across all source files | `@<query>` or auto-detected |
| **History** | Past AI responses, discussion entries | Recent first |
| **Settings** | Configurable settings keys | `setting:<query>` |
### Mode Detection
The palette uses a **prefix-based mode detector**:
- `@` → Symbols mode
- `f:` or `file:` → Files mode
- `setting:` → Settings mode
- No prefix → Commands mode (default)
For Everything mode (Ctrl+Shift+E), all domains are searched and results are merged by relevance score.
### Result Categories
In Everything mode, results are grouped by category:
```
[COMMANDS]
Find in Selection
Fold All
[FILES]
src/gui_2.py
src/aggregate.py
[SYMBOLS]
RAGEngine.search (src/rag_engine.py:242)
BeadsClient.list_beads (src/beads_client.py:73)
[HISTORY]
Last response to "How does X work?"
Discussion entry from 2 hours ago
```
The user can navigate within or across categories using arrow keys.
---
## Usage Patterns
### Quick File Access
1. `Ctrl+Shift+P` to open the palette.
2. Type `f:gui_2` to filter to files matching "gui_2".
3. Press Enter to open the file.
### Quick Symbol Lookup
1. `Ctrl+Shift+P`.
2. Type `@RAGEngine.search`.
3. Press Enter to navigate to the symbol.
### Cross-Domain Search
1. `Ctrl+Shift+E` to open Everything mode.
2. Type `compression`.
3. See: Run Discussion Compression command, `run_discussion_compression` symbol, history entries mentioning "compression", settings for compression strategy.
### Triggering Built-in Commands
1. `Ctrl+Shift+P`.
2. Type a few letters of the command name.
3. Press Enter to execute.
### Keyboard Shortcuts
Some commands have direct keyboard shortcuts (e.g., `Ctrl+S` for Save). These bypass the palette entirely. The palette is for commands without a direct shortcut, or for discovering commands.
---
## Configuration
```toml
[command_palette]
max_results = 20 # Max results shown in palette
history_depth = 100 # How many history entries to search
search_threads = 2 # Parallelism for async searches
fuzzy_match_min_score = 0.3 # Min score to include a result
```
| Key | Default | Description |
|---|---|---|
| `max_results` | 20 | Max results shown. Increase for power users with many commands. |
| `history_depth` | 100 | Number of recent history entries to search. |
| `search_threads` | 2 | Parallel workers for async search. Higher = faster on multi-core, more memory. |
| `fuzzy_match_min_score` | 0.3 | Minimum score to include a result. Lower = more permissive. |
---
## Performance
| Operation | Cost | Notes |
|---|---|---|
| Fuzzy match (commands only) | ~1ms | O(N) over command list. |
| File content search | 50-500ms | Scales with codebase size and query. |
| Symbol search | 100ms-2s | Requires `py_find_usages` etc. |
| History search | ~10ms | In-memory deque. |
| Everything mode (all domains) | 200ms-3s | Parallel across threads. |
The async worker ensures that even the slowest searches don't block the render thread. Results stream in as they become available.
--- ---
## Testing ## Testing
### Unit Tests 13 tests cover the Command Palette:
- `tests/test_command_palette.py` — Fuzzy matcher, command registry, mode detection ### Unit Tests (`tests/test_command_palette.py`)
- `tests/test_command_palette_sim.py` — End-to-end via `live_gui`: open palette, type, select command
```python
def test_fuzzy_match_prefix_ranks_first():
candidates = [
_cmd("find", "Find in Selection"),
_cmd("fold", "Fold All"),
_cmd("config", "Configure Settings"),
]
results = fuzzy_match("fin", candidates, top_n=10)
assert results[0].command.id == "find"
assert results[0].score > 0.5
```
5 unit tests cover:
- `fuzzy_match` prefix ranking
- `fuzzy_match` subsequence matching
- `fuzzy_match` no-match returns empty
- `fuzzy_match` top_n limits results
- `fuzzy_match` exact-prefix priority
- `CommandRegistry` has core commands registered
### Live GUI Tests (`tests/test_command_palette_sim.py`)
7 integration tests use the `live_gui` fixture and `ApiHookClient`:
- `test_palette_starts_hidden` — palette is closed at startup
- `test_palette_toggles_via_callback``_toggle_command_palette` opens and closes
- `test_palette_registers_core_commands` — registry has expected commands with actions
- `test_palette_query_state_resets_on_open` — state resets on close+reopen
- `test_palette_close_helper_resets_all_state``_close_palette` clears per-open state
- `test_execute_runs_command_and_closes``_execute` runs commands and catches exceptions
- `test_fuzzy_match_returns_top_n_for_navigation``top_n=20` is meaningful
### Test Pattern ### Test Pattern
```python ```python
def test_fuzzy_match_ranks_prefix_first(): def test_my_command_via_palette(live_gui):
from src.command_palette import fuzzy_match client = ApiHookClient()
# Open the palette
results = fuzzy_match("fin", ["Find in Selection", "Fold All", "Configure Settings"]) client.push_event("custom_callback", {
assert results[0].command.title == "Find in Selection" "callback": "_toggle_command_palette",
assert results[0].score > 0.5 "args": [],
})
def test_palette_executes_command(live_gui): time.sleep(0.5)
client.open_command_palette() assert client.get_value("show_command_palette") is True
client.type_in_palette("find") # ... verify state, close, etc.
client.select_first_result()
# Verify the Find panel is now open
state = client.get_window_state("find_panel")
assert state["visible"] == True
``` ```
### Async Testing The `_toggle_command_palette` callback is registered specifically for tests, since the keyboard shortcut (Ctrl+Shift+P) cannot be simulated via the Hook API.
Async search is harder to test deterministically. The pattern is: ### Running the Tests
```python ```bash
def test_async_search_streams_results(live_gui): uv run pytest tests/test_command_palette.py tests/test_command_palette_sim.py -v
client.open_command_palette(mode="everything")
client.type_in_palette("compression")
# Wait for streaming to complete (with timeout)
for _ in range(30): # 3 seconds at 100ms intervals
results = client.get_palette_results()
if len(results) > 0 and all(r.complete for r in results):
break
time.sleep(0.1)
# Verify expected results appeared
titles = [r.title for r in client.get_palette_results()]
assert any("Compression" in t for t in titles)
``` ```
Expected: 13/13 pass.
--- ---
## Limitations ## Limitations
1. **No Plugin System Yet**: User-defined commands via TOML are limited to shell, python, or palette-chaining. A full plugin API is roadmap. 1. **No Async Search**: All matching is synchronous. The ~1ms cost is fine for the current command count but won't scale to thousands of commands.
2. **No User-Defined Commands via Config**: Custom commands must be added in Python via `src/commands.py`. There is no `config.toml` integration yet.
2. **No Command Aliases**: A command can have one title but not multiple aliases. Users who remember different names for the same command are out of luck. 3. **No Plugin System**: External packages cannot register their own commands.
4. **No Command Aliases**: A command has one ID and one title. Users who remember a different name for the same action must fuzzy-search for it.
3. **Fuzzy Match Is Synchronous**: For a small command list (~50) this is fine, but if the registry grows to thousands (via plugins), the per-keystroke match could become slow. 5. **No Command History**: Recently-used commands aren't tracked across sessions.
6. **Categories Not Rendered**: The `category` field is stored but not displayed in the palette UI yet.
4. **No Cross-Session Command History**: "Recently used" commands aren't tracked across sessions. Each session starts with the full list. 7. **No `shortcut` field display**: Commands can declare a shortcut but it's not currently shown or used to bind a key.
5. **Async Search Has No Result Caching**: Repeated identical searches re-run the worker. A small LRU cache could improve perceived performance.
6. **No Command Descriptions in Match Score**: A command's description doesn't affect fuzzy match score, only the title does. Users who know the description but not the title may have to guess.
--- ---
## Future Work ## Future Work
- **Plugin System** — Allow third-party commands to be registered via plugins. - **"Everything" mode** — search across commands, files, symbols, history, settings (requires async worker + indexer)
- **Command Aliases** — Multiple names per command. - **Config-defined commands** — `[[command_palette.custom_commands]]` in `config.toml` for shell/python actions
- **Cross-Session History** — Track recently used commands. - **Plugin system** — allow third-party packages to register commands at import time
- **Result Caching** — LRU cache for async search results. - **Command aliases** — multiple titles per command
- **Natural Language Queries** — "send a message to the AI about X" instead of explicit commands. - **Cross-session history** — track recently used commands
- **Command Macros** — Combine multiple commands into one palette entry. - **Render category column** — group results visually by category
- **Show shortcut hints** — display the `shortcut` field next to each result
- **Natural language queries** — "send a message to the AI about X" instead of explicit commands
See [guide_architecture.md](guide_architecture.md) for the async pattern and [guide_simulations.md](guide_simulations.md) for the testing infrastructure. See [guide_architecture.md](guide_architecture.md) for the overall architecture and [guide_simulations.md](guide_simulations.md) for the test infrastructure.
+46 -42
View File
@@ -3,7 +3,7 @@
;;;<<<ImGui_655921752_Default>>>;;; ;;;<<<ImGui_655921752_Default>>>;;;
[Window][Debug##Default] [Window][Debug##Default]
Pos=60,60 Pos=540,400
Size=400,400 Size=400,400
Collapsed=0 Collapsed=0
@@ -44,20 +44,20 @@ Collapsed=0
DockId=0x00000010,0 DockId=0x00000010,0
[Window][Message] [Window][Message]
Pos=1816,32 Pos=166,28
Size=2024,2128 Size=1514,1172
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
[Window][Response] [Window][Response]
Pos=0,32 Pos=0,28
Size=1814,2128 Size=164,1172
Collapsed=0 Collapsed=0
DockId=0x00000010,5 DockId=0x00000010,4
[Window][Tool Calls] [Window][Tool Calls]
Pos=1816,32 Pos=166,28
Size=2024,2128 Size=1514,1172
Collapsed=0 Collapsed=0
DockId=0x00000006,3 DockId=0x00000006,3
@@ -76,8 +76,8 @@ Collapsed=0
DockId=0xAFC85805,2 DockId=0xAFC85805,2
[Window][Theme] [Window][Theme]
Pos=0,32 Pos=0,28
Size=1814,2128 Size=164,1172
Collapsed=0 Collapsed=0
DockId=0x00000010,0 DockId=0x00000010,0
@@ -87,10 +87,10 @@ Size=900,700
Collapsed=0 Collapsed=0
[Window][Diagnostics] [Window][Diagnostics]
Pos=1069,28 Pos=1210,28
Size=1607,1905 Size=1514,1470
Collapsed=0 Collapsed=0
DockId=0x00000006,3 DockId=0x00000006,4
[Window][Context Hub] [Window][Context Hub]
Pos=0,975 Pos=0,975
@@ -105,29 +105,29 @@ Collapsed=0
DockId=0x0000000D,0 DockId=0x0000000D,0
[Window][Discussion Hub] [Window][Discussion Hub]
Pos=1816,32 Pos=166,28
Size=2024,2128 Size=1514,1172
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
[Window][Operations Hub] [Window][Operations Hub]
Pos=0,32 Pos=0,28
Size=1814,2128 Size=164,1172
Collapsed=0
DockId=0x00000010,4
[Window][Files & Media]
Pos=0,32
Size=1814,2128
Collapsed=0 Collapsed=0
DockId=0x00000010,3 DockId=0x00000010,3
[Window][AI Settings] [Window][Files & Media]
Pos=0,32 Pos=0,28
Size=1814,2128 Size=164,1172
Collapsed=0 Collapsed=0
DockId=0x00000010,2 DockId=0x00000010,2
[Window][AI Settings]
Pos=0,28
Size=164,1172
Collapsed=0
DockId=0x00000010,1
[Window][Approve Tool Execution] [Window][Approve Tool Execution]
Pos=3,524 Pos=3,524
Size=416,325 Size=416,325
@@ -140,8 +140,8 @@ Collapsed=0
DockId=0x00000006,2 DockId=0x00000006,2
[Window][Log Management] [Window][Log Management]
Pos=1816,32 Pos=166,28
Size=2024,2128 Size=1514,1172
Collapsed=0 Collapsed=0
DockId=0x00000006,2 DockId=0x00000006,2
@@ -409,10 +409,9 @@ Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
[Window][Project Settings] [Window][Project Settings]
Pos=0,32 Pos=540,400
Size=1814,2128 Size=600,400
Collapsed=0 Collapsed=0
DockId=0x00000010,1
[Window][Undo/Redo History] [Window][Undo/Redo History]
Pos=1529,28 Pos=1529,28
@@ -526,6 +525,11 @@ Pos=61,60
Size=1123,916 Size=1123,916
Collapsed=0 Collapsed=0
[Window][Command Palette##manual_slop]
Pos=540,400
Size=600,400
Collapsed=0
[Table][0xFB6E3870,4] [Table][0xFB6E3870,4]
RefScale=13 RefScale=13
Column 0 Width=80 Column 0 Width=80
@@ -573,11 +577,11 @@ Column 4 Weight=1.0000
Column 5 Width=50 Column 5 Width=50
[Table][0x3751446B,4] [Table][0x3751446B,4]
RefScale=29 RefScale=20
Column 0 Width=87 Column 0 Width=60
Column 1 Width=130 Column 1 Width=89
Column 2 Weight=1.0000 Column 2 Weight=1.0000
Column 3 Width=217 Column 3 Width=149
[Table][0x2C515046,4] [Table][0x2C515046,4]
RefScale=20 RefScale=20
@@ -668,10 +672,10 @@ Column 1 Width=80
Column 2 Width=150 Column 2 Width=150
[Table][0x7804123E,3] [Table][0x7804123E,3]
RefScale=29 RefScale=20
Column 0 Width=29 Column 0 Width=20
Column 1 Weight=1.0000 Column 1 Weight=1.0000
Column 2 Width=746 Column 2 Width=514
[Table][0x09B0112E,3] [Table][0x09B0112E,3]
RefScale=20 RefScale=20
@@ -693,13 +697,13 @@ Column 1 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02 DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,32 Size=3840,2128 Split=X DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1680,1172 Split=X
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2357,1183 Split=X DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2357,1183 Split=X
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2 DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
DockNode ID=0x00000005 Parent=0x0000000B SizeRef=1814,1681 Split=Y Selected=0x3F1379AF DockNode ID=0x00000005 Parent=0x0000000B SizeRef=1208,1681 Split=Y Selected=0x3F1379AF
DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x7BD57D6A DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x0D5A5273
DockNode ID=0x00000011 Parent=0x00000005 SizeRef=983,184 Selected=0x432BAE4E DockNode ID=0x00000011 Parent=0x00000005 SizeRef=983,184 Selected=0x432BAE4E
DockNode ID=0x00000006 Parent=0x0000000B SizeRef=2024,1681 Selected=0x66CFB56E DockNode ID=0x00000006 Parent=0x0000000B SizeRef=1514,1681 Selected=0x2C0206CE
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6 DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=488,1183 Selected=0x3AEC3498 DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=488,1183 Selected=0x3AEC3498