diff --git a/config.toml b/config.toml index 8c9925d4..dab060a1 100644 --- a/config.toml +++ b/config.toml @@ -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 diff --git a/docs/guide_command_palette.md b/docs/guide_command_palette.md index b713e352..cb932fa3 100644 --- a/docs/guide_command_palette.md +++ b/docs/guide_command_palette.md @@ -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:` or auto-detected | -| **Symbols** | Definitions across all source files | `@` or auto-detected | -| **History** | Past AI responses, discussion entries | Recent first | -| **Settings** | Configurable settings keys | `setting:` | - -### 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. diff --git a/manualslop_layout.ini b/manualslop_layout.ini index 4b743de3..88b7bcf1 100644 --- a/manualslop_layout.ini +++ b/manualslop_layout.ini @@ -3,7 +3,7 @@ ;;;<<>>;;; [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