fix(tui): Up/Down keys navigate history instead of scrolling content#79
Merged
yishuiliunian merged 2 commits intomainfrom Apr 4, 2026
Merged
fix(tui): Up/Down keys navigate history instead of scrolling content#79yishuiliunian merged 2 commits intomainfrom
yishuiliunian merged 2 commits intomainfrom
Conversation
…area Up/Down were scrolling the content area instead of navigating input history because `content_overflows` was almost always true after any real conversation. Now Up/Down exclusively handle multiline cursor movement and history navigation; content scrolling is PageUp/PageDown only. Input-mode keys auto-reset scroll_offset to 0 (except PageUp/PageDown/Tab/Esc) so the user always sees the input area when interacting. Removed dead `content_overflows` field and render_progress return value.
yishuiliunian
added a commit
that referenced
this pull request
Apr 5, 2026
…te session titles (#79) Replace the text-based session list with a TUI picker subpage when /resume is invoked without arguments. Filter out sub-agent sessions using a global exclusion set so only root sessions are shown. Auto-set session title from the first user message to make the picker useful. Refactor picker key handling: extract generic Esc/Up/Down/Char/Backspace into handle_generic_picker_key to eliminate duplication between model and session pickers. Split rewind picker and session query methods into separate files to stay within the 200-line limit.
yishuiliunian
added a commit
that referenced
this pull request
Apr 5, 2026
* feat(tui): add interactive session picker for /resume and auto-generate session titles (#79) Replace the text-based session list with a TUI picker subpage when /resume is invoked without arguments. Filter out sub-agent sessions using a global exclusion set so only root sessions are shown. Auto-set session title from the first user message to make the picker useful. Refactor picker key handling: extract generic Esc/Up/Down/Char/Backspace into handle_generic_picker_key to eliminate duplication between model and session pickers. Split rewind picker and session query methods into separate files to stay within the 200-line limit. * fix: correct rustfmt import ordering in session_query and sub_page
yishuiliunian
added a commit
that referenced
this pull request
Apr 5, 2026
…tial response (#79) When an API proxy silently drops SSE connections mid-stream (no `message_stop` event), the agent loop previously treated the truncated response as a normal EndTurn — causing sub-agents to exit with incomplete output (e.g. "Let me create the file." without the Write tool call). Detection: track `received_done` in the stream loop; EOF without Done and without cancel sets `stream_error = true`. Recovery: stream_error with partial content triggers auto-continue (record text, discard incomplete tool_uses, re-call LLM) — same pattern as MaxTokens/PauseTurn. Bounded by `max_auto_continuations`. Cancel safety: cancel paths are excluded from both truncation detection (llm.rs) and auto-continue (turn_exec.rs) to preserve existing cancel behavior. Also extracts stop-hook and observer dispatch into `turn_observer_dispatch.rs` as an independent lifecycle extension point.
yishuiliunian
added a commit
that referenced
this pull request
Apr 5, 2026
…tial response (#82) * fix(runtime): detect SSE stream truncation and auto-continue from partial response (#79) When an API proxy silently drops SSE connections mid-stream (no `message_stop` event), the agent loop previously treated the truncated response as a normal EndTurn — causing sub-agents to exit with incomplete output (e.g. "Let me create the file." without the Write tool call). Detection: track `received_done` in the stream loop; EOF without Done and without cancel sets `stream_error = true`. Recovery: stream_error with partial content triggers auto-continue (record text, discard incomplete tool_uses, re-call LLM) — same pattern as MaxTokens/PauseTurn. Bounded by `max_auto_continuations`. Cancel safety: cancel paths are excluded from both truncation detection (llm.rs) and auto-continue (turn_exec.rs) to preserve existing cancel behavior. Also extracts stop-hook and observer dispatch into `turn_observer_dispatch.rs` as an independent lifecycle extension point. * fix: rustfmt formatting and reorganize tests into ≤200-line files
yishuiliunian
added a commit
that referenced
this pull request
Apr 5, 2026
…ebounce xterm alternate scroll (\x1b[?1007h) converts mouse wheel events into Up/Down arrow keys, creating an unresolvable conflict between content scrolling and history navigation. Previous attempts (#6, #13, #30, #42, #79) toggled priority chains without a lasting fix because the two event sources share the same key codes. Resolve by introducing a 30 ms debounce state machine that exploits the timing difference between mouse wheel bursts (<5 ms between events) and keyboard presses (>20 ms): Idle → Pending (defer 30 ms) → Scrolling (burst confirmed) → History (timer expired / other key) - Mouse wheel: rapid-fire Up/Down detected as burst → scroll content - Keyboard Up/Down: single isolated event → history navigation - Multiline cursor: bypasses debounce entirely (immediate response) - Ctrl+P/N: dedicated history bindings, always immediate - Global/modal/autocomplete keys: discard pending debounce (prevents stale timer from polluting state after Ctrl+C, paste, etc.) Stale timer and dropped-tick resilience: - Pending state checks elapsed time on second arrow to handle delayed timers; stale pending is flushed as history before starting new - Scrolling state has lazy 150 ms expiry check in addition to tick- based cleanup, so dropped ticks don't leave stale scroll mode
yishuiliunian
added a commit
that referenced
this pull request
Apr 5, 2026
…ebounce (#85) * fix(tui): decouple mouse scroll from input history via timing-based debounce xterm alternate scroll (\x1b[?1007h) converts mouse wheel events into Up/Down arrow keys, creating an unresolvable conflict between content scrolling and history navigation. Previous attempts (#6, #13, #30, #42, #79) toggled priority chains without a lasting fix because the two event sources share the same key codes. Resolve by introducing a 30 ms debounce state machine that exploits the timing difference between mouse wheel bursts (<5 ms between events) and keyboard presses (>20 ms): Idle → Pending (defer 30 ms) → Scrolling (burst confirmed) → History (timer expired / other key) - Mouse wheel: rapid-fire Up/Down detected as burst → scroll content - Keyboard Up/Down: single isolated event → history navigation - Multiline cursor: bypasses debounce entirely (immediate response) - Ctrl+P/N: dedicated history bindings, always immediate - Global/modal/autocomplete keys: discard pending debounce (prevents stale timer from polluting state after Ctrl+C, paste, etc.) Stale timer and dropped-tick resilience: - Pending state checks elapsed time on second arrow to handle delayed timers; stale pending is flushed as history before starting new - Scrolling state has lazy 150 ms expiry check in addition to tick- based cleanup, so dropped ticks don't leave stale scroll mode * fix: rustfmt — alphabetical module order in suite.rs + line-length formatting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
scroll_offsetto 0 (except PageUp/PageDown/Tab/Esc)content_overflowsfield andrender_progressreturn valueChanges
crates/loopal-tui/src/input/mod.rs— removedhandle_up_key/handle_down_key, Up/Down now callhandle_up/handle_downdirectly; added auto-scroll-to-bottom logiccrates/loopal-tui/src/input/navigation.rs—handle_up/handle_downresetscroll_offset(covers Ctrl+P/N path)crates/loopal-tui/src/views/progress/mod.rs—render_progressno longer returns overflow flagcrates/loopal-tui/src/render.rs— droppedcontent_overflowsassignmentcrates/loopal-tui/src/app/mod.rs— removedcontent_overflowsfieldcrates/loopal-tui/tests/suite/input_scroll_test.rs— rewrote 12 tests for new behaviorcrates/loopal-tui/tests/suite/input_scroll_edge_test.rs— 8 new edge-case tests (auto-reset, agent-busy, multiline boundary, Ctrl+P/N scroll reset)Test plan
bazel test //crates/loopal-tui:loopal-tui_test— 20 scroll/history tests passbazel build //... --config=clippy— zero warningsbazel build //... --config=rustfmt— format cleanbazel build //...— full workspace build passes