Skip to content

fix(pi-tui): handle above-viewport changes without a destructive redraw - #2133

Open
UoMaster wants to merge 1 commit into
MoonshotAI:mainfrom
UoMaster:fix-issues-2098
Open

fix(pi-tui): handle above-viewport changes without a destructive redraw#2133
UoMaster wants to merge 1 commit into
MoonshotAI:mainfrom
UoMaster:fix-issues-2098

Conversation

@UoMaster

Copy link
Copy Markdown

Related Issue

Resolve #2098

Problem

While a subagent runs, status rows above the visible window (the subagent header's spinner/elapsed timer ticking at 80ms, live progress lines) kept marking content above the viewport as changed. Every such frame took the "first changed line is above the viewport" branch and did a destructive full redraw (ESC[2J/ESC[3J), which cleared scrollback and snapped the terminal back to the bottom — making it impossible to scroll up and read history during subagent execution (macOS and Windows).

What changed

In packages/pi-tui's differential renderer, the above-viewport branch no longer falls back to a destructive full redraw:

  • In-place edits above the window (delta === 0, e.g. the subagent header tick): the diff is clamped to the visible window; when nothing visible changed, nothing is painted and only the frame caches are updated.
  • Length changes above the window (progress lines inserted, step rows collapsing): the viewport anchor follows the content by the delta — the screen already displays the shifted rows — and only the mismatched window rows are repainted in place. No scrolling, no ESC[3J.
  • Destructive fallback kept for the cases that genuinely need it: content collapsing to at/above the window top (/clear, compaction), out-of-range anchors, and frames involving kitty image lines.

This is deliberately narrower than the fork's earlier viewport/scrollback patches (reverted in 23daf0f3): it never scroll-commits and never repaints a rewound window, which is where the blank-screen / duplicated-scrollback edge cases came from. Accepted trade-offs, documented in code and packages/pi-tui/AGENTS.md: scrollback keeps stale bytes for changed above-window rows (a terminal cannot edit scrollback without clearing), and a shrink rewind may later re-commit a bounded span.

Tests: a new TUI above-viewport changes suite pins the behavior — in-place edits, mixed visible edits, a 20-frame subagent-style ticking simulation (asserts zero full redraws, no ESC[3J, byte-identical scrollback), insertion/shrink re-anchoring with commit-continuity checks, mixed shift+edit frames, and the collapse fallback. The pre-existing transient-component test now asserts the non-destructive behavior. Full pi-tui suite (743 tests) and the kimi-code vitest suite (2380 tests) pass.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

A change above the viewport (e.g. a subagent status row ticking during
streaming) triggered a full redraw emitting ESC[2J/ESC[3J, which wiped
scrollback and yanked the user's scroll position to the bottom on every
frame. Clamp in-place edits to the visible window, and re-anchor the
viewport on above-window length changes with only the mismatched window
rows repainted in place — no scrolling, no scrollback clear. Collapses,
out-of-range anchors, and kitty image frames keep the destructive
fallback.

Closes MoonshotAI#2098
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d552357

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@moonshot-ai/kimi-code Patch
@moonshot-ai/pi-tui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d55235737b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// by delta rows. The terminal already displays the shifted rows,
// so re-anchor the viewport and repaint only the window rows
// whose content actually differs — in place, with no scrolling.
const newViewportTop = prevViewportTop + delta;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve append scrolling when earlier rows also change

When a frame both edits a row above the window and appends output at the tail, this line treats the net total length delta as if the insertion happened above the viewport. In that mixed frame (for example, a ticking subagent status row plus a streamed line in the same render batch), the branch repaints the new tail in place and returns without emitting the newline scroll that would commit the old top viewport row to scrollback, so the renderer's previousViewportTop advances while the terminal buffer does not and one history row is lost from scrollback on subsequent appends. The non-destructive re-anchor rule is scoped to length changes above the window, so this path needs to distinguish where the insertion/deletion occurred before re-anchoring.

AGENTS.md reference: packages/pi-tui/AGENTS.md:L14-L14

Useful? React with 👍 / 👎.

}

const delta = newLines.length - this.previousLines.length;
if (delta === 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back before skipping changed Kitty image lines

For same-height changes above the viewport, this branch can skip or clamp past changed Kitty image lines without ever calling deleteChangedKittyImages or doing the documented full-redraw fallback. If an off-screen image placement is replaced/removed while the line count stays the same, previousKittyImageIds is then overwritten with the new union, so the old image ID is no longer tracked for later deletion and stale Kitty graphics can remain in scrollback. The package rule explicitly keeps destructive fallback for frames involving Kitty image lines.

AGENTS.md reference: packages/pi-tui/AGENTS.md:L14-L14

Useful? React with 👍 / 👎.

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.

[Bug] 任务触发子代理(subagent)执行期间,聊天窗口滚动条持续跳回底部,无法查阅历史消息

1 participant