Bundles the in-flight work into the first tagged release. See CHANGELOG.md `[0.0.1] - 2026-05-14` for the full per-change list. Highlights: - Sidebar / chrome stability: clamp absolute cursor positioning and printable bytes to the viewport so long-running TUIs (claude, codex) can't spray into the right rail; bound tab bar's row clear to the viewport width so the rail isn't wiped on every tab redraw; flag scroll escapes (RI/IND/NEL/SU/SD/IL/DL) and clamp `CSI 0/1/2 J`/`K` to viewport columns. - Palette: "Spawn process…" form, macros (`sw `, `k `, `sp `), kill entries mark the focused tab, dead agents drop out of the switch list. - Sidebar: split into Processes (session-wide) + Agent Tree (per-active-agent) sections; relaunch indicator; Ctrl+W/S walks the combined list, Ctrl+A/D steps tabs. - MCP: protocol handshake (`initialize`, `tools/list`, `tools/call`, `ping`), `mcp_injection.kind = cli_override / config_env` so codex and opencode pick up the server with no file writes, `lifecycle` help topic and tool-description cleanup-duty pointers. - Lifecycle: orchestrator-spawned children cascade-killed when the parent dies; orchestrator-injected prompts end with CR + delayed Enter so claude submits cleanly.
This commit is contained in:
@@ -107,6 +107,83 @@ func TestPaletteLegacyPrintableTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// "Spawn process…" is intercepted on accept: it switches the palette
|
||||
// into the form mode instead of closing it. Subsequent Enter on a
|
||||
// non-empty command line emits the submit action with relaunch reflecting
|
||||
// the checkbox state.
|
||||
func TestPaletteSpawnProcessFormFlow(t *testing.T) {
|
||||
p := newPalette(nil, "", preset.Set{})
|
||||
// The "Spawn process…" entry is the only non-Quit item with an
|
||||
// empty preset list. Locate its index by scanning items.
|
||||
idx := -1
|
||||
for i, it := range p.items {
|
||||
if it.action.kind == "spawn-process-form" {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
t.Fatalf("no spawn-process-form item in palette items: %+v", p.items)
|
||||
}
|
||||
p.cursor = idx
|
||||
|
||||
// Enter on the entry opens the form (done=false, mode flips).
|
||||
action, done, _ := p.handleInput([]byte("\r"), 0)
|
||||
if done {
|
||||
t.Fatalf("spawn-process-form accept closed palette: action=%+v", action)
|
||||
}
|
||||
if p.mode != paletteModeSpawnForm || p.form == nil {
|
||||
t.Fatalf("palette did not switch to form mode: mode=%v form=%v", p.mode, p.form)
|
||||
}
|
||||
|
||||
// Type a command: "bun run dev".
|
||||
for _, b := range []byte("bun run dev") {
|
||||
_, _, _ = p.handleInput([]byte{b}, 0)
|
||||
}
|
||||
if string(p.form.cmd) != "bun run dev" {
|
||||
t.Fatalf("form cmd = %q", string(p.form.cmd))
|
||||
}
|
||||
|
||||
// Tab to the relaunch field, toggle with space.
|
||||
_, _, _ = p.handleInput([]byte{'\t'}, 0)
|
||||
if p.form.field != 1 {
|
||||
t.Fatalf("field after tab = %d, want 1", p.form.field)
|
||||
}
|
||||
_, _, _ = p.handleInput([]byte{' '}, 0)
|
||||
if !p.form.relaunch {
|
||||
t.Fatalf("relaunch toggle didn't stick")
|
||||
}
|
||||
|
||||
// Enter submits.
|
||||
action, done, _ = p.handleInput([]byte("\r"), 0)
|
||||
if !done || action.kind != "spawn-process-submit" {
|
||||
t.Fatalf("submit didn't fire: action=%+v done=%v", action, done)
|
||||
}
|
||||
if action.command != "bun run dev" || !action.relaunch {
|
||||
t.Fatalf("submit payload = %+v", action)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaletteSpawnProcessFormEmptyCommandCancels(t *testing.T) {
|
||||
p := newPalette(nil, "", preset.Set{})
|
||||
p.mode = paletteModeSpawnForm
|
||||
p.form = &spawnProcessForm{}
|
||||
action, done, _ := p.handleInput([]byte("\r"), 0)
|
||||
if !done || action.kind != "cancel" {
|
||||
t.Fatalf("empty submit didn't cancel: action=%+v done=%v", action, done)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaletteSpawnProcessFormEscCancels(t *testing.T) {
|
||||
p := newPalette(nil, "", preset.Set{})
|
||||
p.mode = paletteModeSpawnForm
|
||||
p.form = &spawnProcessForm{cmd: []rune("x")}
|
||||
action, done, _ := p.handleInput([]byte{0x1b}, 0)
|
||||
if !done || action.kind != "cancel" {
|
||||
t.Fatalf("ESC didn't cancel form: action=%+v done=%v", action, done)
|
||||
}
|
||||
}
|
||||
|
||||
// peekArrowEvent powers the chunk-level dedupe in processStdin. The
|
||||
// scenarios below cover the patterns we've actually seen terminals
|
||||
// emit for one physical Down press: a kitty press event, a legacy CSI
|
||||
|
||||
Reference in New Issue
Block a user