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
crt_filter_enabled = false
separate_task_dag = false
separate_usage_analytics = true
separate_usage_analytics = false
separate_tier1 = false
separate_tier2 = false
separate_tier3 = false
@@ -33,7 +33,7 @@ separate_tier4 = false
separate_external_tools = false
[gui.show_windows]
"Project Settings" = true
"Project Settings" = false
"Files & Media" = true
"AI Settings" = true
"MMA Dashboard" = false
+245 -308
View File
@@ -6,422 +6,359 @@
## 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 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.
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.
This guide covers:
1. **Architecture** — Where the Command Palette sits in the input pipeline
2. **Commands Registry** — How commands are registered
3. **Fuzzy Search** — Matching algorithm and ranking
4. **Async Context Preview** — Why context preview is async and what it does
5. **"Everything" Mode** — Cross-domain search
6. **Testing** — Test patterns
1. **Architecture** — Where the Command Palette sits in the render pipeline
2. **Implementation** — File layout and the `Command` data model
3. **Commands Registry** — How commands are registered
4. **Fuzzy Search** — Matching algorithm and scoring
5. **Built-in Commands**The 11+ commands shipped today
6. **Adding Custom Commands** — How to extend the library
7. **Testing** — Unit and integration coverage
8. **Limitations & Future Work**
---
## 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
│ - Detects Ctrl+Shift+P → opens palette modal
│ - Modal is rendered at the top of the stack
│ - User types in the search box │
│ - Fuzzy matcher ranks results in real time │
│ - "Everything" mode triggers async context │
│ preview worker │
│ Render Loop (_gui_func in src/gui_2.py)
│ - Detects Ctrl+Shift+P → toggles palette
│ - Calls render_palette_modal(self, commands)
└─────────────────┬───────────────────────────────┘
reads commands from
calls
┌─────────────────────────────────────────────────┐
Commands Registry
│ - Built-in commands (file ops, MMA, view, etc.)
│ - User-defined commands (from config)
│ - Plugin commands (future)
render_palette_modal(app, commands)
│ - Sets window focus on first open
│ - Captures arrow/enter keys BEFORE input_text
│ - Renders centered 600x400 modal
│ - Handles Up/Down/Enter/Escape │
└─────────────────┬───────────────────────────────┘
fuzzy-matched against
reads from
┌─────────────────────────────────────────────────┐
Fuzzy Matcher (synchronous)
│ - Returns ranked list of (command, score)
│ - Updates as user types
│ - Supports fuzzy subsequence matching │
CommandRegistry (in src/command_palette.py)
│ - registry.all() returns List[Command]
│ - Commands are registered at module import time
└─────────────────┬───────────────────────────────┘
for "Everything" mode, queries
populated by
┌─────────────────────────────────────────────────┐
Async Context Preview Worker
│ - Reads file content, symbols, history
│ - Runs in background thread to avoid frame
stutter
│ - Results stream into the palette incrementally │
src/commands.py — 11+ static commands
│ - Decorated with @registry.register
│ - Each has id, title, category, action
- All actions take app: App as parameter
└─────────────────────────────────────────────────┘
```
**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:
| Category | Examples |
| File | Role |
|---|---|
| **File** | Open File, Save File, Close File, Revert File |
| **Edit** | Find, Replace, Go to Line, Go to Symbol |
| **View** | Toggle Panel, Switch Theme, Switch Profile |
| **AI** | Send to AI, Compress Discussion, Show Comms Log |
| **MMA** | Start Track, Pause Engine, Resume Engine, Kill Worker |
| **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 |
| `src/command_palette.py` | `Command` dataclass, `CommandRegistry`, `fuzzy_match`, `render_palette_modal`, `_close_palette`, `_execute` helpers |
| `src/commands.py` | Static command definitions (~11+ commands) |
| `src/gui_2.py` | `App.show_command_palette` flag, `_toggle_command_palette` method, Ctrl+Shift+P handler in `_gui_func`, modal render call |
| `tests/test_command_palette.py` | 6 unit tests |
| `tests/test_command_palette_sim.py` | 7 live_gui integration tests |
### Command Schema
### The `Command` Data Model
```python
@dataclass
class Command:
id: str # Unique identifier
title: str # Display name
category: str # Category for grouping
shortcut: Optional[str] # Keyboard shortcut (e.g., "Ctrl+S")
description: str = "" # Optional help text
enabled_when: Optional[str] = None # Condition for enablement (e.g., "has_selection")
action: Callable # Function to execute when selected
id: str # Unique identifier
title: str # Display name (fuzzy-matched)
category: str # Category label (e.g., "File", "View")
shortcut: Optional[str] = None # Reserved for direct shortcuts (not yet rendered)
description: str = "" # Optional help text
enabled_when: Optional[str] = None # Reserved for future conditional enablement
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
def get_all_commands() -> List[Command]:
"""Returns all registered commands."""
class CommandRegistry:
def __init__(self) -> None:
self._commands: Dict[str, Command] = {}
def get_command_by_id(command_id: str) -> Optional[Command]:
"""Returns a command by its ID, or None if not found."""
def register(self, command_or_callable: Any) -> Any:
"""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
[command_palette.custom_commands.my_format]
title = "Format Selected File"
category = "Custom"
action_type = "shell"
action = "powershell -File format_script.ps1 {current_file}"
shortcut = "Ctrl+Shift+F"
```python
def render_palette_modal(app: Any, commands: List[Command]) -> None:
"""Renders the Command Palette as a centered modal. Called from _gui_func."""
if not getattr(app, "show_command_palette", False):
return
# ... sets up window focus on first open ...
# ... 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:
- `"shell"` — Run a shell command (with `{current_file}`, `{current_selection}`, etc. as placeholders)
- `"python"` — Call a Python function (function must be importable)
- `"palette"` — Open another palette with filtered commands
### The Keyboard Handler
In `App._gui_func`, immediately after the existing Ctrl+Alt+R (hot reload) handler:
```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
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
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.
2. **Score calculation**: If subsequence matches, calculates a score based on:
- **Exact prefix match** (highest score): Query is a prefix of the title
- **Word boundary match**: Query starts at a word boundary in the title
- **Contiguous match**: All matched characters are contiguous in the title
- **Character distance**: Smaller gaps between matched characters score higher
3. **Result sorting**: Results are sorted by score (descending). The top N (configurable, default 20) are displayed.
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):
- **Exact prefix match** (+1.0): Query is a prefix of the title
- **Word boundary match** (+0.5): Query starts at a word boundary in the title
- **Contiguous match** (+0.3): All matched characters are contiguous
- **Gap penalty** (-0.1 per gap): Penalty for non-contiguous matches
3. **Sort by score descending**, return top N (default 20).
### Example Matches
Query: `"fld"` (user wants "Find in Selection")
Query: `"fld"` against titles:
| Title | Match | Score |
|---|---|---|
| "Find in Selection" | subsequence `f-i-n-d in S-e-L-ection` → no | 0 (rejected) |
| "Fold All" | subsequence `F-o-L-D` → yes | high (exact prefix) |
| "File List" | subsequence `F-i-L-e` → yes | medium (gaps) |
The result list shows "Fold All" first, then "File List".
| "Fold All" | exact prefix | 1.0 (prefix) + 0.3 (contiguous) = 1.3 |
| "File List" | subsequence | 0 (gaps > 0) |
| "Find in Selection" | rejected | no subsequence match |
### 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)
- **Symbol search** (definitions, references via `py_find_usages` etc.)
- **History search** (past AI responses, discussion entries)
| ID | Title | Category | Action |
|---|---|---|---|
| `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:
```
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:
Every command action is wrapped in a defensive `hasattr` check:
```python
class AsyncSearchWorker:
def __init__(self):
self._results_queue: queue.Queue = queue.Queue()
self._stop_event: threading.Event = threading.Event()
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
@registry.register
def clear_discussion(app: "App") -> None:
"""Clear Discussion — Clear all entries in the current discussion."""
if hasattr(app, "discussion_history"):
app.discussion_history = []
```
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
def on_query_changed(self, new_query: str) -> None:
if self.current_worker:
self.current_worker.cancel() # Sets _stop_event
self.current_worker = AsyncSearchWorker()
self.current_worker.search(new_query)
@registry.register
def my_new_command(app: "App") -> None:
"""My New Command — Does something useful."""
if hasattr(app, "my_attr"):
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 |
|---|---|---|
| **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.
The arrow keys are processed BEFORE the input field consumes them, ensuring Up/Down navigate the list and not just the text cursor.
---
## Testing
### Unit Tests
13 tests cover the Command Palette:
- `tests/test_command_palette.py` — Fuzzy matcher, command registry, mode detection
- `tests/test_command_palette_sim.py` — End-to-end via `live_gui`: open palette, type, select command
### Unit Tests (`tests/test_command_palette.py`)
```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
```python
def test_fuzzy_match_ranks_prefix_first():
from src.command_palette import fuzzy_match
results = fuzzy_match("fin", ["Find in Selection", "Fold All", "Configure Settings"])
assert results[0].command.title == "Find in Selection"
assert results[0].score > 0.5
def test_palette_executes_command(live_gui):
client.open_command_palette()
client.type_in_palette("find")
client.select_first_result()
# Verify the Find panel is now open
state = client.get_window_state("find_panel")
assert state["visible"] == True
def test_my_command_via_palette(live_gui):
client = ApiHookClient()
# Open the palette
client.push_event("custom_callback", {
"callback": "_toggle_command_palette",
"args": [],
})
time.sleep(0.5)
assert client.get_value("show_command_palette") is True
# ... verify state, close, etc.
```
### 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
def test_async_search_streams_results(live_gui):
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)
```bash
uv run pytest tests/test_command_palette.py tests/test_command_palette_sim.py -v
```
Expected: 13/13 pass.
---
## 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.
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. **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.
4. **No Cross-Session Command History**: "Recently used" commands aren't tracked across sessions. Each session starts with the full list.
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.
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.
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.
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.
7. **No `shortcut` field display**: Commands can declare a shortcut but it's not currently shown or used to bind a key.
---
## Future Work
- **Plugin System** — Allow third-party commands to be registered via plugins.
- **Command Aliases** — Multiple names per command.
- **Cross-Session History** — Track recently used commands.
- **Result Caching** — LRU cache for async search results.
- **Natural Language Queries** — "send a message to the AI about X" instead of explicit commands.
- **Command Macros** — Combine multiple commands into one palette entry.
- **"Everything" mode** — search across commands, files, symbols, history, settings (requires async worker + indexer)
- **Config-defined commands** — `[[command_palette.custom_commands]]` in `config.toml` for shell/python actions
- **Plugin system** — allow third-party packages to register commands at import time
- **Command aliases** — multiple titles per command
- **Cross-session history** — track recently used commands
- **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>>>;;;
[Window][Debug##Default]
Pos=60,60
Pos=540,400
Size=400,400
Collapsed=0
@@ -44,20 +44,20 @@ Collapsed=0
DockId=0x00000010,0
[Window][Message]
Pos=1816,32
Size=2024,2128
Pos=166,28
Size=1514,1172
Collapsed=0
DockId=0x00000006,0
[Window][Response]
Pos=0,32
Size=1814,2128
Pos=0,28
Size=164,1172
Collapsed=0
DockId=0x00000010,5
DockId=0x00000010,4
[Window][Tool Calls]
Pos=1816,32
Size=2024,2128
Pos=166,28
Size=1514,1172
Collapsed=0
DockId=0x00000006,3
@@ -76,8 +76,8 @@ Collapsed=0
DockId=0xAFC85805,2
[Window][Theme]
Pos=0,32
Size=1814,2128
Pos=0,28
Size=164,1172
Collapsed=0
DockId=0x00000010,0
@@ -87,10 +87,10 @@ Size=900,700
Collapsed=0
[Window][Diagnostics]
Pos=1069,28
Size=1607,1905
Pos=1210,28
Size=1514,1470
Collapsed=0
DockId=0x00000006,3
DockId=0x00000006,4
[Window][Context Hub]
Pos=0,975
@@ -105,29 +105,29 @@ Collapsed=0
DockId=0x0000000D,0
[Window][Discussion Hub]
Pos=1816,32
Size=2024,2128
Pos=166,28
Size=1514,1172
Collapsed=0
DockId=0x00000006,1
[Window][Operations Hub]
Pos=0,32
Size=1814,2128
Collapsed=0
DockId=0x00000010,4
[Window][Files & Media]
Pos=0,32
Size=1814,2128
Pos=0,28
Size=164,1172
Collapsed=0
DockId=0x00000010,3
[Window][AI Settings]
Pos=0,32
Size=1814,2128
[Window][Files & Media]
Pos=0,28
Size=164,1172
Collapsed=0
DockId=0x00000010,2
[Window][AI Settings]
Pos=0,28
Size=164,1172
Collapsed=0
DockId=0x00000010,1
[Window][Approve Tool Execution]
Pos=3,524
Size=416,325
@@ -140,8 +140,8 @@ Collapsed=0
DockId=0x00000006,2
[Window][Log Management]
Pos=1816,32
Size=2024,2128
Pos=166,28
Size=1514,1172
Collapsed=0
DockId=0x00000006,2
@@ -409,10 +409,9 @@ Collapsed=0
DockId=0x00000006,1
[Window][Project Settings]
Pos=0,32
Size=1814,2128
Pos=540,400
Size=600,400
Collapsed=0
DockId=0x00000010,1
[Window][Undo/Redo History]
Pos=1529,28
@@ -526,6 +525,11 @@ Pos=61,60
Size=1123,916
Collapsed=0
[Window][Command Palette##manual_slop]
Pos=540,400
Size=600,400
Collapsed=0
[Table][0xFB6E3870,4]
RefScale=13
Column 0 Width=80
@@ -573,11 +577,11 @@ Column 4 Weight=1.0000
Column 5 Width=50
[Table][0x3751446B,4]
RefScale=29
Column 0 Width=87
Column 1 Width=130
RefScale=20
Column 0 Width=60
Column 1 Width=89
Column 2 Weight=1.0000
Column 3 Width=217
Column 3 Width=149
[Table][0x2C515046,4]
RefScale=20
@@ -668,10 +672,10 @@ Column 1 Width=80
Column 2 Width=150
[Table][0x7804123E,3]
RefScale=29
Column 0 Width=29
RefScale=20
Column 0 Width=20
Column 1 Weight=1.0000
Column 2 Width=746
Column 2 Width=514
[Table][0x09B0112E,3]
RefScale=20
@@ -693,13 +697,13 @@ Column 1 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
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=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
DockNode ID=0x00000005 Parent=0x0000000B SizeRef=1814,1681 Split=Y Selected=0x3F1379AF
DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x00000005 Parent=0x0000000B SizeRef=1208,1681 Split=Y Selected=0x3F1379AF
DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x0D5A5273
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=0x00000004 Parent=0xAFC85805 SizeRef=488,1183 Selected=0x3AEC3498