fix: reset popup scroll state synchronously on new prompt (#83)#84
Closed
fix: reset popup scroll state synchronously on new prompt (#83)#84
Conversation
The popup occasionally rendered on top of the prompt after Ctrl+C because `scroll_deficit` and `last_layout` only reset via the async CPR roundtrip — a window large enough for typing to race in and a stale `scroll_deficit=N` to anchor the popup at `prompt_row`. Add a synchronous `prompt_started` flag on `TerminalState`, set on every OSC 133;A and OSC 7771;A. Drain it at every handler render entry (render, render_at, try_merge_dynamic, navigation, accept chaining, trigger) and reset both fields. Also reset `scroll_deficit` in `dismiss()`. CPR-sync fallback in `trigger` retained.
- Replace test_render_resets_scroll_deficit_on_prompt_started and test_try_merge_dynamic_resets_scroll_deficit_on_prompt_started with direct unit tests on drain_prompt_started_reset that assert last_layout.is_none() and scroll_deficit == 0 after the drain. - Add test_drain_prompt_started_reset_no_flag_preserves_state to pin the no-op-when-unset contract. - Add test_handle_resize_does_not_consume_prompt_started. - Cover both-flags-set case in test_trigger_resets_last_layout_on_prompt_started_only. - Drop redundant drain_prompt_started_reset calls in process_key_visible navigate paths; render() drains internally. - Remove "issue #83" cross-references from comments per CLAUDE.md. - Tighten scroll_deficit field doc and drain_prompt_started_reset doc.
Owner
Author
|
Old PR |
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
Fixes #83 — completion popup occasionally renders on top of the prompt after Ctrl+C on Terminal.app and Ghostty.
Root cause
InputHandler::scroll_deficit(crates/gc-pty/src/handler.rs:189) was only reset via the async CPR roundtrip drained insidetrigger()(handler.rs:614). After Ctrl+C, the shell prints^C\n+ new prompt +OSC 7771;A+OSC 7770("")in one batched read. Task B writesCSI 6nand immediately callstrigger()withbuffer_dirty=true— but the terminal's CPR response is still in flight on stdin.take_cpr_synced()returnsfalse,scroll_deficitstays at its pre-Ctrl+C value, anddismiss()(handler.rs:1050) didn't reset it either.If the user types within that window,
final_cursor_row = real_row − scroll_deficitproducesstart_row = real_row— the popup anchor lands on the new prompt row. Withscroll_deficit=1you get exactly the symptom from the issue:(b<popup>Book-Air GitHub % suwith the popup overlaid at column 2.Both terminals are affected because the race lives in the proxy/handler — not in DECSET 2026 vs pre-render-buffer.
Fix
Add a synchronous prompt-boundary signal independent of CPR. Drain it at every render entry so a re-render that fires before the CPR roundtrip lands still sees a clean per-prompt state.
gc-parser/src/state.rs— newprompt_started: boolfield onTerminalState. Publictake_prompt_started()mirrorstake_cpr_synced(atomic drain).pub(crate) mark_prompt_started()setter.gc-parser/src/performer.rs— callmark_prompt_started()inside both OSC 133;A and OSC 7771;A handlers, alongside the existingrequest_cursor_sync/set_prompt_rowcalls.gc-pty/src/handler.rs:drain_prompt_started_resethelper called at the start ofrender,try_merge_dynamic, navigation inprocess_key_visible, andaccept_with_chaining. On true:scroll_deficit = 0andlast_layout = None.trigger()drains both flags.prompt_startedresets both fields;cpr_synced(fallback) only resetsscroll_deficitbecause a CPR sync within the same prompt context shouldn't blow away a valid layout.dismiss()now resetsscroll_deficit = 0.scroll_deficitupdated to reflect all reset paths.CPR-sync reset retained as defense-in-depth.
Test plan
cargo fmt --checkcargo clippy --all-targets -- -D warningscargo test --workspace— 942 passed, 0 failedprompt_started_flag_default_falseprompt_started_set_then_drainedtest_osc133_prompt_a_sets_prompt_startedtest_osc7771_prompt_a_sets_prompt_startedtest_render_resets_scroll_deficit_on_prompt_startedtest_dismiss_resets_scroll_deficittest_try_merge_dynamic_resets_scroll_deficit_on_prompt_startedtest_trigger_resets_last_layout_on_prompt_started_only