Private
Public Access
0
0
Files
manual_slop/docs/superpowers/specs/2026-06-02-command-palette-design.md
T

223 lines
7.7 KiB
Markdown

# 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.