Skip to content

fix(tui): stale content after tree navigation#2126

Open
Perlence wants to merge 1 commit intobadlogic:mainfrom
Perlence:fix/tui-stale-content-after-tree-navigation
Open

fix(tui): stale content after tree navigation#2126
Perlence wants to merge 1 commit intobadlogic:mainfrom
Perlence:fix/tui-stale-content-after-tree-navigation

Conversation

@Perlence
Copy link
Contributor

@Perlence Perlence commented Mar 13, 2026

Problem

When navigating the session tree with "No summary", stale messages from the old branch remain on screen and duplicate on repeated navigation.

Here's a repro case that the clanker has crafted:

repro-stale-tree-nav.zip

And here's the recording of the bug in that session and the fix:

Screen.Recording.2026-03-14.at.17.25.57.mov

Root cause

The differential renderer decides whether to fall back to fullRender by checking if firstChanged is above the viewport:

const previousContentViewportTop = Math.max(0, this.previousLines.length - height);
if (firstChanged < previousContentViewportTop) {
    fullRender(true);

This uses previousLines.length to estimate the viewport position. But maxLinesRendered, which determines the actual viewport, can be larger than previousLines.length when transient UI components (tree selector, extension selector) inflated it during the same interaction. The tree selector adds ~40 extra lines, then closes, but maxLinesRendered doesn't shrink.

This was done in 0925faf which changed the check from firstChanged < viewportTop (using maxLinesRendered) to firstChanged < previousContentViewportTop (using previousLines.length) to avoid false positive redraws when appending lines after content shrunk. However, the differential renderer genuinely can't address lines above maxLinesRendered - height.

Fix

Use prevViewportTop (derived from maxLinesRendered) for the check, restoring the original correct behavior:

if (firstChanged < prevViewportTop) {
    fullRender(true);

This may trigger a few more fullRender calls when maxLinesRendered is inflated, but those redraws are necessary for correctness.

When navigating the session tree, transient UI components (tree
selector, extension selector) inflate maxLinesRendered beyond the actual
content height. After navigation, the differential renderer checked
whether firstChanged was above the viewport using previousLines.length -
height, which underestimated the true viewport position. This caused it
to enter the differential path instead of doing a full re-render,
leaving old branch content (summary, messages) on screen. Repeated
navigation duplicated the stale content.

Fix: use prevViewportTop (derived from maxLinesRendered) for the
above-viewport check so the renderer correctly falls back to fullRender
when firstChanged is above the actual viewport.
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