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:
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCursorShifterCUP(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[H"))
|
||||
want := []byte("\x1b[2;1H")
|
||||
if !bytes.Equal(got, want) {
|
||||
@@ -15,7 +15,7 @@ func TestCursorShifterCUP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterCUPRowCol(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[10;5H"))
|
||||
if string(got) != "\x1b[11;5H" {
|
||||
t.Fatalf("CUP 10;5: got %q", got)
|
||||
@@ -23,7 +23,7 @@ func TestCursorShifterCUPRowCol(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterVPA(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[7d"))
|
||||
if string(got) != "\x1b[8d" {
|
||||
t.Fatalf("VPA 7: got %q", got)
|
||||
@@ -31,7 +31,7 @@ func TestCursorShifterVPA(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterDECSTBM(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[2;20r"))
|
||||
if string(got) != "\x1b[3;21r" {
|
||||
t.Fatalf("DECSTBM: got %q", got)
|
||||
@@ -43,7 +43,7 @@ func TestCursorShifterDECSTBM(t *testing.T) {
|
||||
// region — that's what was scrolling claude's content up after a
|
||||
// focus switch from codex.
|
||||
func TestCursorShifterDECSTBMEmptyResetsToViewport(t *testing.T) {
|
||||
cs := newCursorShifter(3, 36) // mainTop=4, childRows=36
|
||||
cs := newCursorShifter(3, 36, 80) // mainTop=4, childRows=36
|
||||
got := cs.Shift([]byte("\x1b[r"))
|
||||
if string(got) != "\x1b[4;39r" {
|
||||
t.Fatalf("empty DECSTBM reset: got %q want \\x1b[4;39r", got)
|
||||
@@ -51,7 +51,7 @@ func TestCursorShifterDECSTBMEmptyResetsToViewport(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterPrivateCSIPassthrough(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
// Alt-screen toggle — private CSI.
|
||||
got := cs.Shift([]byte("\x1b[?1049h"))
|
||||
if string(got) != "\x1b[?1049h" {
|
||||
@@ -60,7 +60,7 @@ func TestCursorShifterPrivateCSIPassthrough(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterSGRPassthrough(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[1;31mhello\x1b[0m"))
|
||||
if string(got) != "\x1b[1;31mhello\x1b[0m" {
|
||||
t.Fatalf("SGR: got %q", got)
|
||||
@@ -68,7 +68,7 @@ func TestCursorShifterSGRPassthrough(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterStraddleChunks(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
a := cs.Shift([]byte("\x1b["))
|
||||
b := cs.Shift([]byte("5;3H"))
|
||||
got := string(a) + string(b)
|
||||
@@ -78,7 +78,7 @@ func TestCursorShifterStraddleChunks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCursorShifterOSCNotRewritten(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36)
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
// OSC body containing what looks like a CSI cursor move — should
|
||||
// NOT be rewritten.
|
||||
in := []byte("\x1b]0;\x1b[5;3Htitle\x07")
|
||||
@@ -87,3 +87,27 @@ func TestCursorShifterOSCNotRewritten(t *testing.T) {
|
||||
t.Fatalf("OSC: got %q want %q", got, in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorShifterClampsCUPColumn(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[5;120H"))
|
||||
if string(got) != "\x1b[6;80H" {
|
||||
t.Fatalf("CUP col 120 should clamp to childCols=80: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorShifterClampsCHAColumn(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36, 80)
|
||||
got := cs.Shift([]byte("\x1b[120G"))
|
||||
if string(got) != "\x1b[80G" {
|
||||
t.Fatalf("CHA col 120 should clamp to childCols=80: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorShifterCUPNoClampWhenChildColsZero(t *testing.T) {
|
||||
cs := newCursorShifter(1, 36, 0)
|
||||
got := cs.Shift([]byte("\x1b[5;120H"))
|
||||
if string(got) != "\x1b[6;120H" {
|
||||
t.Fatalf("childCols=0 should disable col clamping: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user