# Command Palette Implementation & Tests **Date:** 2026-06-02 **Status:** Draft (pending review) **Parent Track:** `command_palette_and_performance_20260602` (continuing Phase 2 + adding Phase 3) **Spec:** `conductor/tracks/command_palette_and_performance_20260602/spec.md` (existing) --- ## Context & Motivation A `command_palette_and_performance_20260602` track was started in early June 2026. **Phase 1 (Async Context Preview)** is complete. **Phase 2 (Command Palette)** is unstarted — no `src/command_palette.py`, no `src/commands.py`, no test files. The user reports the palette doesn't pop up on `Ctrl+Shift+P`. The existing spec says `Ctrl+P`; this design uses `Ctrl+Shift+P` (per the user's expectation and VSCode convention documented in `docs/guide_command_palette.md`). This design finishes Phase 2 of the existing track and adds Phase 3 (tests). --- ## Scope ### In Scope - `src/command_palette.py` — Module-level: `Command` dataclass, `CommandRegistry`, `fuzzy_match()`, `render_palette_modal(app)`, `render_everything_modal(app)` - `src/commands.py` — Static command definitions (~30-50 commands across categories) - `src/gui_2.py` — `self.show_command_palette: bool` in `App.__init__`, `_render_command_palette(self)` thin wrapper, `Ctrl+Shift+P` keyboard handler - `tests/test_command_palette.py` — Unit tests for fuzzy matcher, command registry, mode detection - `tests/test_command_palette_sim.py` — Integration tests via `live_gui` ### Out of Scope - Async context preview (Phase 1; already complete) - The "Everything" mode async search worker (mentioned in the existing spec; defer to a follow-up track) - Visual theming of the palette (use existing ImGui style) --- ## Design ### Data Model: `Command` ```python @dataclass class Command: id: str # Unique identifier title: str # Display name category: str # Category for grouping shortcut: Optional[str] # Optional default shortcut (e.g., "Ctrl+S") description: str = "" # Optional help text enabled_when: Optional[str] = None # Optional condition expression action: Callable = None # Function to execute when selected ``` ### Command Registry ```python # src/commands.py from src.command_palette import Command, CommandRegistry registry = CommandRegistry() @registry.register def save_file(app: App) -> None: """Save File — File category, Ctrl+S""" # ... call app's save logic ``` **Registration patterns:** - Decorator: `@registry.register` for top-level functions - Explicit: `registry.register(Command(id=..., title=..., action=...))` for closures or classes ### Fuzzy Matcher Implemented in `src/command_palette.py` as a pure function: ```python def fuzzy_match(query: str, candidates: List[Command], top_n: int = 20) -> List[ScoredCommand]: """ Returns the top_n candidates matching query, ranked by score. Algorithm: 1. Subsequence check: query chars must appear in title, in order 2. Score calculation: - Exact prefix match: +1.0 - Word boundary match: +0.5 - Contiguous match: +0.3 - Character distance penalty: -0.1 per gap 3. Sort by score descending 4. Return top_n """ ``` ### Modal Rendering The palette is a centered ImGui modal. Module-level function (per delegation pattern): ```python # src/command_palette.py def render_palette_modal(app: App) -> None: """Render the Command Palette modal. Called from gui_2.py when app.show_command_palette is True.""" if not app.show_command_palette: return imgui.set_next_window_position(...) # Centered imgui.set_next_window_size(...) if imgui.begin("Command Palette##palette", closable=True): # Search input # Fuzzy-matched results list # Keyboard navigation imgui.end() ``` ### Keyboard Handler In `gui_2.py`'s main event loop: ```python io = imgui.get_io() if io.key_ctrl and io.key_shift and imgui.is_key_pressed(imgui.Key.p): app.show_command_palette = not app.show_command_palette ``` --- ## File Structure - `src/command_palette.py` — NEW: Command, CommandRegistry, fuzzy_match, render_palette_modal - `src/commands.py` — NEW: Static command definitions and registry - `src/gui_2.py` — MODIFY: add `self.show_command_palette`, add `_render_command_palette` wrapper, add Ctrl+Shift+P handler, register the palette module - `tests/test_command_palette.py` — NEW: Unit tests - `tests/test_command_palette_sim.py` — NEW: Integration tests via live_gui --- ## Tests ### Unit Tests (`tests/test_command_palette.py`) ```python def test_fuzzy_match_prefix_ranks_first(): from src.command_palette import fuzzy_match candidates = [ Command(id="find", title="Find in Selection"), Command(id="fold", title="Fold All"), Command(id="config", title="Configure Settings"), ] results = fuzzy_match("fin", candidates) assert results[0].command.id == "find" assert results[0].score > 0.5 def test_fuzzy_match_rejects_no_match(): from src.command_palette import fuzzy_match candidates = [Command(id="x", title="foo bar")] results = fuzzy_match("xyz", candidates) assert len(results) == 0 def test_command_registry_register_and_list(): from src.command_palette import CommandRegistry from src.commands import registry assert "save_file" in registry.all() # All commands have id, title, category for cmd in registry.all(): assert cmd.id and cmd.title and cmd.category def test_command_registry_duplicate_raises(): from src.command_palette import CommandRegistry, Command reg = CommandRegistry() reg.register(Command(id="x", title="X", category="test")) with pytest.raises(ValueError): reg.register(Command(id="x", title="X", category="test")) ``` ### Integration Tests (`tests/test_command_palette_sim.py`) ```python def test_ctrl_shift_p_opens_palette(live_gui): client = live_gui[1] # Press Ctrl+Shift+P client.press_key_combo("Ctrl+Shift+P") # Verify the palette is visible state = client.get_window_state("command_palette") assert state["visible"] == True def test_palette_filters_as_user_types(live_gui): client = live_gui[1] client.press_key_combo("Ctrl+Shift+P") client.type_in_palette("save") results = client.get_palette_results() assert any("Save" in r.title for r in results) # Other commands not shown assert not any("Compress" in r.title for r in results) def test_palette_executes_command_on_enter(live_gui): client = live_gui[1] client.press_key_combo("Ctrl+Shift+P") client.type_in_palette("Reset") client.press_key("Down") client.press_key("Enter") # Verify the reset command was executed (check via Hook API) state = client.get_session_state() assert state.get("discussion_history", []) == [] ``` --- ## Acceptance Criteria - `Ctrl+Shift+P` opens the palette (verified via `live_gui` test) - Typing in the palette filters results via fuzzy match - Selecting a command (Enter key) executes it and closes the palette - Escape closes the palette without executing - All unit tests pass - All integration tests pass - The palette respects the existing theme (dark/light/nerv) - No new lint errors --- ## Risks 1. **Keyboard handler conflicts:** The Ctrl+Shift+P combo might be intercepted by other subsystems. Mitigation: check for other handlers in the codebase first; if conflicts, document them. 2. **Pyodide build dependencies:** The image_bundle web backend (for Track 4) has a different architecture than this track. The two are independent but should be aware of each other. 3. **Test flakiness:** `live_gui` tests can be flaky if the GUI doesn't initialize in time. Mitigation: the standard 15-second readiness polling is sufficient.