From 35dd37a1c9edb8720712afaa69bee57ab1a62a95 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 22 Oct 2026 09:02:46 +0100 Subject: [PATCH 01/15] fix(tui): isolate composer history and pane borders (#2535) --- tui/cmd/rune-tui/main_test.go | 36 +++------- tui/lanes/lanes_test.go | 22 ++++++ tui/lanes/render.go | 2 +- tui/nav/input3.go | 86 +++-------------------- tui/nav/input3_test.go | 127 ++++++++-------------------------- 5 files changed, 73 insertions(+), 200 deletions(-) diff --git a/tui/cmd/rune-tui/main_test.go b/tui/cmd/rune-tui/main_test.go index 11d688ec..d5749e35 100644 --- a/tui/cmd/rune-tui/main_test.go +++ b/tui/cmd/rune-tui/main_test.go @@ -1341,12 +1341,9 @@ func TestBackspaceEditsComposerText(t *testing.T) { } } -// TestComposerHistoryWalk (old-TUI HistoryInput adoption): up/down -// walk the sent history with save/restore of the in-progress text — -// bare arrows while the chat input is active never move the lane -// selection. -func TestComposerHistoryWalk(t *testing.T) { - configuredHome(t) // the walk reads the persisted history — sandbox it +// TestComposerArrowsNeverRecallSentHistory proves shell routing leaves +// an in-progress draft intact after submissions have been recorded. +func TestComposerArrowsNeverRecallSentHistory(t *testing.T) { configuredHome(t) fc := &fakeChatCompleter{} bootCompleter = fc @@ -1369,28 +1366,15 @@ func TestComposerHistoryWalk(t *testing.T) { } next, _ := m.Update(keyPress("x")) // in-progress text m = next.(model) - next, _ = m.Update(arrowPress(tea.KeyUp)) - m = next.(model) - if got := m.composer.Text(); got != "second" { - t.Fatalf("up: text = %q, want \"second\"", got) - } - next, _ = m.Update(arrowPress(tea.KeyUp)) - m = next.(model) - if got := m.composer.Text(); got != "first" { - t.Fatalf("up twice: text = %q, want \"first\"", got) - } - next, _ = m.Update(arrowPress(tea.KeyDown)) - m = next.(model) - if got := m.composer.Text(); got != "second" { - t.Fatalf("down: text = %q, want \"second\"", got) - } - next, _ = m.Update(arrowPress(tea.KeyDown)) - m = next.(model) - if got := m.composer.Text(); got != "x" { - t.Fatalf("down past newest: text = %q, want the stashed \"x\"", got) + for _, key := range []rune{tea.KeyUp, tea.KeyDown, tea.KeyUp, tea.KeyDown} { + next, _ = m.Update(arrowPress(key)) + m = next.(model) + if got := m.composer.Text(); got != "x" { + t.Fatalf("arrow %d recalled sent history into draft: got %q, want \"x\"", key, got) + } } if st := m.face.Snapshot(); st.Focus != semantic.FocusChat { - t.Fatalf("history arrows moved focus to %q", st.Focus) + t.Fatalf("composer arrows moved focus to %q", st.Focus) } } diff --git a/tui/lanes/lanes_test.go b/tui/lanes/lanes_test.go index d5ef948a..3b7e168d 100644 --- a/tui/lanes/lanes_test.go +++ b/tui/lanes/lanes_test.go @@ -724,6 +724,28 @@ func TestFocusedPanePinkAccent(t *testing.T) { } } +func TestNestedANSIResetCannotResetPaneBorder(t *testing.T) { + content := []string{"\x1b[31mstyled\x1b[0m"} + for _, tc := range []struct { + name string + focus bool + color string + }{ + {name: "focused pink", focus: true, color: "\x1b[38;2;255;121;198m"}, + {name: "unfocused color 240", focus: false, color: "\x1b[38;5;240m"}, + } { + t.Run(tc.name, func(t *testing.T) { + rows := strings.Split(drawPane(12, 3, "", tc.focus, content), "\n") + if !strings.HasPrefix(rows[1], tc.color+"│") { + t.Fatalf("left edge does not locally reassert border style: %q", rows[1]) + } + if !strings.HasSuffix(rows[1], tc.color+"│\x1b[m") { + t.Fatalf("right edge does not locally reassert border style after SGR reset: %q", rows[1]) + } + }) + } +} + // TestTaskPaneSectioning: the task pane sections its work stream (the // task rows) from its own entry area — the faint rule and the entry // signage (D41 task-pane structure). diff --git a/tui/lanes/render.go b/tui/lanes/render.go index 4968279e..54478148 100644 --- a/tui/lanes/render.go +++ b/tui/lanes/render.go @@ -555,7 +555,7 @@ func drawPane(w, h int, _ string, focused bool, content []string) string { var b []string b = append(b, style.Render("┌"+strings.Repeat("─", inner)+"┐")) for _, c := range content { - b = append(b, style.Render("│"+c+"│")) + b = append(b, style.Render("│")+c+style.Render("│")) } b = append(b, style.Render("└"+strings.Repeat("─", inner)+"┘")) return strings.Join(b, "\n") diff --git a/tui/nav/input3.go b/tui/nav/input3.go index ea1749cd..faf11a1f 100644 --- a/tui/nav/input3.go +++ b/tui/nav/input3.go @@ -4,17 +4,11 @@ import ( "strings" ) -// ── the 3-line input: the old TUI contract on the rebindable map ────── +// ── the 3-line input on the rebindable map ──────────────────────────── // -// The old TUI design contract (docs/tui-design-capture/rune_tui.md -// §2.2 + §2.3) pins the input surface: a FIXED 3-line input with the -// placeholder "Type in