feat(tui): command palette with fuzzy search #53

Merged
charles merged 1 commit from tui/palette-18 into main 2026-04-11 20:41:48 +00:00
Owner

Summary

Sixth PR in the loom-tui stack. Adds a command palette overlay backed by nucleo-matcher.

Stacks on #52. Closes charles/loom#18.

What's in

  • components::palette::PaletteAction — id + human name + scope + current binding
  • components::palette::build_registry — walks KeyMap::iter() so displayed bindings reflect whatever overrides tui.toml applied
  • components::palette::filter — nucleo-matcher scoring; empty query returns all actions alphabetically
  • components::palette::PaletteState — push/pop/clear query, select next/prev with clamping, current accessor
  • PaletteOverlay in app.rs — wraps PaletteState, handles Esc/Enter/arrow keys/Backspace/Ctrl+U/printable chars, swallows everything else so underlying screens don't see input
  • AppAction::InvokeAction(ActionId) — new variant so the overlay can enqueue a keybind firing for the next tick; drain_actions routes it through invoke_action
  • APP_PALETTE action now pushes the palette overlay (: or Ctrl+P)
  • components promoted to a directory: components/mod.rs (sidebar, status bar, layout) + components/palette.rs

Tests (5 new, 32 total)

  • Registry contains every known action exactly once
  • Empty query returns all actions alphabetically
  • Fuzzy query "gallery" ranks a gallery action first
  • Selection clamps at both ends
  • Push/pop/clear char updates query()

Notes

  • The palette re-builds its registry each time it's pushed so live key-map edits (#39) get reflected without overlay cache invalidation
  • PaletteOverlay::handle returns true for every key event — the overlay is modal and should never leak input to the underlying screen
  • query() is #[cfg(test)]-only since nothing else reads it externally today
## Summary Sixth PR in the loom-tui stack. Adds a command palette overlay backed by `nucleo-matcher`. Stacks on #52. Closes charles/loom#18. ## What's in - **`components::palette::PaletteAction`** — id + human name + scope + current binding - **`components::palette::build_registry`** — walks `KeyMap::iter()` so displayed bindings reflect whatever overrides `tui.toml` applied - **`components::palette::filter`** — nucleo-matcher scoring; empty query returns all actions alphabetically - **`components::palette::PaletteState`** — push/pop/clear query, select next/prev with clamping, `current` accessor - **`PaletteOverlay`** in `app.rs` — wraps `PaletteState`, handles Esc/Enter/arrow keys/Backspace/Ctrl+U/printable chars, swallows everything else so underlying screens don't see input - **`AppAction::InvokeAction(ActionId)`** — new variant so the overlay can enqueue a keybind firing for the next tick; `drain_actions` routes it through `invoke_action` - **`APP_PALETTE`** action now pushes the palette overlay (`:` or `Ctrl+P`) - **`components`** promoted to a directory: `components/mod.rs` (sidebar, status bar, layout) + `components/palette.rs` ## Tests (5 new, 32 total) - Registry contains every known action exactly once - Empty query returns all actions alphabetically - Fuzzy query `"gallery"` ranks a gallery action first - Selection clamps at both ends - Push/pop/clear char updates `query()` ## Notes - The palette re-builds its registry each time it's pushed so live key-map edits (#39) get reflected without overlay cache invalidation - `PaletteOverlay::handle` returns `true` for every key event — the overlay is modal and should never leak input to the underlying screen - `query()` is `#[cfg(test)]`-only since nothing else reads it externally today
Adds a command palette overlay backed by nucleo-matcher. Opens on
APP_PALETTE (`:` or `Ctrl+P`), closes on Esc, executes on Enter.
Up/Down moves selection. The results list is sourced from a registry
built at push-time from the current KeyMap so displayed bindings
reflect the effective overrides from tui.toml.

The palette sends AppAction::InvokeAction(id) on Enter; a new
AppAction variant lets overlays enqueue keybind firings without
re-wiring their own executor. The App routes drained InvokeAction
entries through invoke_action, so the palette can trigger any
global action just like a key press would.

The components module is promoted to a directory with a
components::palette submodule holding PaletteAction, build_registry,
fuzzy filter, and PaletteState.

Closes charles/loom#18

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
charles changed target branch from tui/navigation-17 to main 2026-04-11 20:41:41 +00:00
charles deleted branch tui/palette-18 2026-04-11 20:41:48 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/loom!53
No description provided.