Auto-snap child viewport to bottom when typing into scrollback

Typing into a focused child while its emulator viewport was
scrolled up left the keystroke heading to the PTY but the input
box invisible below the visible region — it looked like typing
did nothing. processStdin's flushForward now sets
pendingViewportBottom whenever bytes are actually injected, so
the existing post-loop handler snaps the viewport and repaints.

Wheel events and Ctrl-B paths are untouched: both are intercepted
before reaching forward, so wheel still scrolls into history and
Ctrl-B is still the explicit escape hatch. Only bytes that would
actually reach the child PTY trigger the snap.
This commit is contained in:
2026-05-15 15:34:00 +01:00
parent b5dfaf39c4
commit 24c8183832
2 changed files with 20 additions and 8 deletions

View File

@@ -7,6 +7,12 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Fixed
- Typing into a focused child while its emulator viewport is
scrolled up into scrollback history now auto-snaps the viewport
back to the live area. Previously the keystroke reached the
child PTY but the input box was off-screen below the visible
region, so it looked like typing did nothing. Wheel scrolling
and Ctrl-B are unchanged; only forwarded keystrokes snap.
- Top tab bar now keeps the top-level agent's tab highlighted
when focus is on one of its sub-agents (or on a Processes pane
entry, matching the existing agent-tree behavior). Previously

View File

@@ -1323,6 +1323,15 @@ func (st *uiState) processStdin(chunk []byte) {
}
forward := make([]byte, 0, len(chunk))
var pendingAction *paletteAction
var pendingNav navEntry
var pendingRestartID string
var pendingViewportDelta int
var pendingViewportBottom bool
var pendingPadStep int
var pendingPadExit bool
flushForward := func() {
if len(forward) == 0 {
return
@@ -1337,19 +1346,16 @@ func (st *uiState) processStdin(chunk []byte) {
if prev != OwnerUser {
go st.drawStatusLine()
}
// Auto-snap the emulator viewport to the live area
// on any forwarded keystroke. Without this, typing
// while scrolled into history leaves the cursor /
// echoed bytes off-screen below the visible region.
pendingViewportBottom = true
}
}
forward = forward[:0]
}
var pendingAction *paletteAction
var pendingNav navEntry
var pendingRestartID string
var pendingViewportDelta int
var pendingViewportBottom bool
var pendingPadStep int
var pendingPadExit bool
// childOnPrimary captures whether the focused child is on its primary
// screen at the start of this chunk. Wheel events on the primary
// screen scroll the emulator viewport (inline scrollback); on the