Skip to content

fix(tui): commit queued-drain echo through the scrollback handoff - #227

Merged
elkaix merged 3 commits into
mainfrom
fix/tui-queued-input-erase-race
Jul 21, 2026
Merged

fix(tui): commit queued-drain echo through the scrollback handoff#227
elkaix merged 3 commits into
mainfrom
fix/tui-queued-input-erase-race

Conversation

@elkaix

@elkaix elkaix commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a rare "queued follow-up ghost": when a follow-up is queued mid-turn and later drained, the input card's top border (──── ● off) occasionally fossilizes into scrollback just above the executed command — visible only under heavy CPU load.

Root cause

The shell echoes each drained queued command with a raw console.print(render_user_echo_text(...)) (ui/shell/__init__.py). Once the first turn has ended, running_prompt_hide_input_card() returns False, so the input card is painted. The raw print triggers prompt_toolkit's run_in_terminal erase/redraw with that border still in the pre-handoff frame; under load the teardown's erase-height drifts and strands the border in scrollback.

This is the asymmetry: streamed turn content commits through _run_scrollback_handoff (which raises the handoff depth → hide-gate True → invalidate — before emitting), but the queued-drain echo did neither.

Fix

Add _PromptLiveView.commit_scrollback_echo(renderable) — a thin wrapper that routes the echo through _run_scrollback_handoff, so the input card is hidden at the instant the echo is written, keeping the border out of the frame the terminal teardown erases. The shell drain loop now awaits it instead of the raw print. This makes the queued-drain echo consistent with how streamed content is committed.

Verification

  • Deterministic regression test (test_commit_scrollback_echo_hides_input_card_during_emit): drives the real method and asserts the hide-gate is True at the exact moment the echo commits. Confirmed non-circular — it fails ([False] == [True]) when the method is reverted to a raw print, and passes with the fix.
  • Under load: the manual PTY regression test (test_mid_turn_queued_input_renders_once_and_executes_once, CI-skipped) passed 25/25 under an 8×yes CPU load with the fix, where the old code fossilized under the same load.
  • Full suite: make check-pythinker-code clean; make test-pythinker-code green (8289 passed, 7 skipped, 1 xfailed; e2e 65 passed).

Sibling echo sites (audited, no-defer)

  • _emit_steer_echo (_interactive.py) — safe by design: commits via _append_pending_scrollback_flush_pending_scrollback → handoff, not a raw print.
  • _echo_agent_input (initial-input echo, __init__.py) — verified safe: fires at stable idle geometry with no view/delegate attached (where the erase-height doesn't drift); the two existing initial-echo fossil guards passed 12/12 under the same 8×yes load.

Summary by CodeRabbit

  • Bug Fixes

    • Resolved a rare “ghost card” condition where queued follow-up input could leave an input-card border “fossilized” in terminal scrollback during heavy load.
    • Queued command “resolved” echoes now use the active prompt’s scrollback handoff flow, ensuring the input card hides correctly during queued echo emission.
  • Tests

    • Added regression coverage for queued echo rendering during input-card hide/teardown, including best-effort handling when the handoff fails.

Echoing a drained queued command with a raw console.print left the input card
in the frame prompt_toolkit's run_in_terminal teardown erases, so under heavy
CPU load the card's top border fossilized into scrollback just above the echoed
command (the rare "queued follow-up ghost").

Route the echo through the view's scrollback handoff via a new
_PromptLiveView.commit_scrollback_echo, which raises the handoff depth first so
running_prompt_hide_input_card is True at the instant the echo is written —
keeping the border out of the torn-down frame, exactly as streamed turn content
is committed.

Adds a deterministic regression test asserting the card is hidden when the echo
commits (verified to fail on the raw-print path). The steer echo already commits
via pending-scrollback/handoff; the initial-input echo runs at stable idle
geometry with no view and was verified fossil-free under heavy load.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 136ee4e3-db60-4738-912e-6d3caaadb9d3

📥 Commits

Reviewing files that changed from the base of the PR and between ed85014 and 1c725ff.

📒 Files selected for processing (1)
  • tests/ui_and_conv/test_visualize_running_prompt.py

📝 Walkthrough

Walkthrough

Queued follow-up command echoes now use the prompt view’s scrollback handoff instead of direct console printing. Tests verify input-card hiding, state restoration, and best-effort handling when handoff teardown fails.

Changes

Queued echo handoff

Layer / File(s) Summary
Route queued echoes through scrollback handoff
src/pythinker_code/ui/shell/visualize/_interactive.py, src/pythinker_code/ui/shell/__init__.py, CHANGELOG.md
Adds commit_scrollback_echo() and uses it while draining queued messages, with best-effort error handling and changelog documentation.
Verify echo handoff behavior
tests/ui_and_conv/test_visualize_running_prompt.py
Checks input-card visibility during emission, state restoration afterward, and suppression of handoff failures.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Shell
  participant PromptLiveView
  participant Console
  Shell->>PromptLiveView: Commit queued command echo
  PromptLiveView->>Console: Print inside scrollback handoff
  Console-->>PromptLiveView: Emit echo and restore input-card state
Loading

Possibly related PRs

Suggested labels: bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits and accurately describes the queued-drain echo scrollback fix.
Description check ✅ Passed It covers the summary, root cause, fix, and verification, but omits the issue link section and checklist items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tui-queued-input-erase-race

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pythinker_code/ui/shell/__init__.py`:
- Around line 1500-1507: Update the queue handling around commit_scrollback_echo
so the queued command remains available until the handoff succeeds. Avoid
removing it with pending.pop(0) before the await; remove or acknowledge the item
only after a successful commit, while preserving explicit failure cleanup so the
command is retried or reported as dropped.

In `@tests/ui_and_conv/test_visualize_running_prompt.py`:
- Around line 737-778: Add a caller-level async regression test for
commit_scrollback_echo that forces its scrollback handoff/emission to fail, then
assert the queued command remains available for retry or is explicitly reported
as dropped according to the existing contract. Keep the successful
hidden-input-card assertions unchanged and cover the failure or partial-success
outcome without introducing unrelated behavior.
- Around line 767-772: Update the test around
view.running_prompt_hide_input_card() to stop patching
shared_console._force_terminal, which is Rich’s private field. Create a public
Console(force_terminal=False) instance and patch the module’s console reference
with it, while preserving the existing print interception and assertion
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f2ab6d5-38a2-4961-bcd0-7aa3c9ee7b98

📥 Commits

Reviewing files that changed from the base of the PR and between c0b7aee and 9de0ba8.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/pythinker_code/ui/shell/__init__.py
  • src/pythinker_code/ui/shell/visualize/_interactive.py
  • tests/ui_and_conv/test_visualize_running_prompt.py

Comment thread src/pythinker_code/ui/shell/__init__.py
Comment thread tests/ui_and_conv/test_visualize_running_prompt.py
Comment thread tests/ui_and_conv/test_visualize_running_prompt.py
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Address review on the queued-follow-up ghost fix:
- commit_scrollback_echo now logs a failed scrollback handoff instead of
  raising it. The echo is cosmetic and the command still runs via run_soul, so
  a handoff error must not drop the queued command the shell already popped.
  Cancellation still propagates (CancelledError is not an Exception).
- Add a failure-path regression asserting the best-effort behavior.
- Stop patching Rich's private _force_terminal in the hide-card test; use a
  public Console(force_terminal=False) swapped into the module reference.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/ui_and_conv/test_visualize_running_prompt.py`:
- Around line 784-813: Update
test_commit_scrollback_echo_is_best_effort_when_handoff_fails to exercise the
real console handoff instead of monkeypatching the private
_run_scrollback_handoff method. Arrange for a non-terminal Console.print
operation to raise, invoke commit_scrollback_echo, and assert that the print
seam was reached while the exception remains swallowed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3362368-e787-479b-a7e6-d52eceda4fb7

📥 Commits

Reviewing files that changed from the base of the PR and between 9de0ba8 and ed85014.

📒 Files selected for processing (2)
  • src/pythinker_code/ui/shell/visualize/_interactive.py
  • tests/ui_and_conv/test_visualize_running_prompt.py

Comment thread tests/ui_and_conv/test_visualize_running_prompt.py Outdated
Instead of monkeypatching the private _run_scrollback_handoff, make a
non-terminal Console.print raise so the handoff runs its real emit and
commit_scrollback_echo swallows the failure from the actual seam. Asserts the
print was reached and no exception propagated.
@elkaix

elkaix commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@elkaix

elkaix commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@elkaix
elkaix merged commit 49b8ca7 into main Jul 21, 2026
38 checks passed
@elkaix
elkaix deleted the fix/tui-queued-input-erase-race branch July 21, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant