Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions internal/tui/coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,17 @@ func TestStatusLinePlacement(t *testing.T) {
if marker < 0 || thinking < marker {
t.Error("status line must render below the last user message")
}
if !strings.HasPrefix(m.statusLine(), "\n") {
t.Error("status line must lead with a blank separator row")
}
if strings.Contains(plain(m.header()), "thinking") {
t.Error("header must not carry the busy indicator anymore")
}

// The row claims exactly one line of layout; idle releases it, and a
// pending approval (which owns the input area) never shows it.
if got, want := m.inputAreaHeight(), inputHeight+1; got != want {
// The indicator claims two rows of layout — a blank separator above it,
// then the status row itself; idle releases both, and a pending approval
// (which owns the input area) never shows it.
if got, want := m.inputAreaHeight(), inputHeight+2; got != want {
t.Errorf("busy inputAreaHeight = %d, want %d", got, want)
}
m.busy = false
Expand Down
5 changes: 5 additions & 0 deletions internal/tui/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func (m *Model) submit() tea.Cmd {
m.ta.Reset()
m.closeAC()
m.refresh()
m.vp.GotoBottom() // Enter means "show me the latest", even mid-turn
return nil
}
m.ta.Reset()
Expand All @@ -136,6 +137,10 @@ func (m *Model) sendPrompt(text string) tea.Cmd {
}
m.relayout() // the busy status line claims a row above the input
m.refresh()
// Submitting is an explicit "show me the latest" signal — jump to the
// bottom even when the reader was up in the scrollback (refresh alone
// only sticks when already at the bottom).
m.vp.GotoBottom()

thinking := ""
if m.thinkOn {
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ func (m *Model) inputAreaHeight() int {
}
h := inputHeight
if m.statusLineVisible() {
h++ // busy status line above the input box
h += 2 // busy status line + blank separator row above the input box
}
if m.ac.open {
h += m.ac.height()
Expand Down
28 changes: 28 additions & 0 deletions internal/tui/promptflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,34 @@ func TestQueuedPromptSendsOnDone(t *testing.T) {
}
}

// TestSubmitJumpsToBottom verifies ⏎ always returns the viewport to the
// latest output — both for a fresh prompt and when queueing mid-turn — even
// when the reader was up in the scrollback.
func TestSubmitJumpsToBottom(t *testing.T) {
m := newTestModel()
m.ta.Focus()
tallTranscript(m)

// Queueing mid-turn: busy model, scrolled up, ⏎.
busyTurn(m)
m.vp.GotoTop()
m.ta.SetValue("follow up")
m.submit()
if !m.vp.AtBottom() {
t.Error("queueing a prompt should jump to the latest output")
}

// Fresh submit once the turn ended (the returned send cmd is deliberately
// not executed — newTestModel has no client).
m.busy = false
m.vp.GotoTop()
m.ta.SetValue("next prompt")
m.submit()
if !m.vp.AtBottom() {
t.Error("submitting a prompt should jump to the latest output")
}
}

// tallTranscript loads a scrollable assistant message into the transcript.
// (A markdown list survives glamour as one rendered line per item; a plain
// "x\n" repeat would collapse into a single wrapped paragraph.)
Expand Down
4 changes: 3 additions & 1 deletion internal/tui/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ func (m *Model) statusLine() string {
if e := m.elapsed(); e != "" {
el = th.headerMeta.Render(" · " + e)
}
return th.spinner.Render(m.sp.View()) + " " + th.statusBusy.Render(label) + el
// A blank row above separates the indicator from the transcript tail —
// inputAreaHeight accounts for it so the layout math stays exact.
return "\n" + th.spinner.Render(m.sp.View()) + " " + th.statusBusy.Render(label) + el
}

// statusLineVisible reports whether the status line occupies a row, keeping
Expand Down