Overhaul command palette UX

Six-phase sweep: section headers (Focused / Open / Spawn / Quit) with
header-skip cursor; chip strip mirroring sw/sp/k macros, driven by
Tab; unified Spawn verbs across agent / process / terminal / custom;
dropped duplicate global Close list in favor of Ctrl-X inline close
on a Switch row plus the [Close] chip; scored matching (prefix >
word-boundary > substring > fuzzy) with matched-char highlighting;
title bar surfaces focus subject; rename forms split long subject
onto its own row; new Alt-1..9 quick-pick, Home/End, ? help overlay,
and Ctrl-R relaunch toggle inside the spawn-process form. Scroll
indicator and cursor/total counter round out the footer.
This commit is contained in:
2026-05-15 16:41:44 +01:00
parent 0c960fa859
commit 81bc77366f
6 changed files with 1299 additions and 233 deletions

View File

@@ -47,36 +47,50 @@ func TestPaletteBareEscCancels(t *testing.T) {
}
}
// firstSelectable returns the lowest item index whose action is
// selectable (not a section header), or -1 if the palette has no
// selectable rows.
func firstSelectable(p *paletteState) int {
for i, it := range p.items {
if it.action.kind != "header" {
return i
}
}
return -1
}
func TestPaletteKittyArrowsNavigate(t *testing.T) {
pr := []*preset.Preset{{Name: "a"}, {Name: "b"}, {Name: "c"}}
p := newPalette(nil, "", "", preset.Set{Agents: pr})
if p.cursor != 0 {
t.Fatalf("initial cursor %d", p.cursor)
first := firstSelectable(p)
if first < 0 || p.cursor != first {
t.Fatalf("initial cursor %d, want first selectable %d", p.cursor, first)
}
// Kitty functional Down arrow.
_, _, adv := p.handleInput([]byte("\x1b[57353u"), 0)
if adv != 8 {
t.Fatalf("advance %d", adv)
}
if p.cursor != 1 {
t.Fatalf("cursor %d after Down, want 1", p.cursor)
if p.cursor != first+1 {
t.Fatalf("cursor %d after Down, want %d", p.cursor, first+1)
}
// Kitty functional Up arrow.
_, _, _ = p.handleInput([]byte("\x1b[57352u"), 0)
if p.cursor != 0 {
t.Fatalf("cursor %d after Up, want 0", p.cursor)
if p.cursor != first {
t.Fatalf("cursor %d after Up, want %d", p.cursor, first)
}
}
func TestPaletteLegacyArrowsStillWork(t *testing.T) {
pr := []*preset.Preset{{Name: "a"}, {Name: "b"}}
p := newPalette(nil, "", "", preset.Set{Agents: pr})
first := firstSelectable(p)
_, _, adv := p.handleInput([]byte("\x1b[B"), 0)
if adv != 3 {
t.Fatalf("advance %d", adv)
}
if p.cursor != 1 {
t.Fatalf("cursor %d, want 1", p.cursor)
if p.cursor != first+1 {
t.Fatalf("cursor %d, want %d", p.cursor, first+1)
}
}