Summary
When a long assistant response or final summary is rendered in the interactive TUI, the beginning of the response can disappear from the terminal UI and native scrollback.
The missing area is not blank. Rows from an earlier tool call remain visible in their place, which makes it look as though the previous tool component overwrote the first part of the assistant response.
This was observed while a final summary was streaming after a large Bash/agent-browser component and while background-job completion notifications were also being rendered.
User impact
Important context at the beginning of a final answer can become unreadable, including:
- the summary or conclusion of a completed task;
- warnings and limitations;
- the premise needed to interpret later bullet points;
- instructions that precede verification or commit lists.
The text appears to exist in the logical assistant response, but it is not recoverable by scrolling the terminal upward.
Observed example
The first visible part of the final summary was:
- 4ce0e48 fix(sharing): align public lookup and privacy defaults
- d76ecf2 feat(security): add actionable proxy diagnostics
### Verification
There should have been additional assistant text before those lines. Instead, the rows immediately above them still showed the previous Bash component:
gajae
Testing keyboard interaction and snapshot
┌─── ✔ Bash ───────────────────────────────────────────────────────┐
│ $ agent-browser --session proxy-boundary ... │
├─── Output ───────────────────────────────────────────────────────┤
The stale Bash rows and the middle of the assistant summary are contiguous in terminal scrollback.
Screenshots


The first screenshot shows stale Bash/tool rows immediately above a summary that begins in the middle. The second shows the completed response and background-job notifications, with the missing earlier summary text still unavailable.
Environment
- gjc version:
v0.12.5
- Source inspected:
v0.12.6
- OS:
- Terminal emulator and version:
- Terminal dimensions at reproduction:
$TERM:
$TERM_PROGRAM:
- Multiplexer such as tmux/screen/psmux:
- Local terminal, SSH, WSL, or remote session:
Steps to reproduce
The original occurrence involved a complex agent run, but the following conditions appear important:
- Start an interactive gjc session in a terminal whose transcript is already taller than one viewport.
- Run a task that produces a multi-line Bash or tool component.
- Have one or more background tasks active so completion notifications can be rendered after or around the final assistant response.
- Let the assistant begin a final response that is longer than the available viewport.
- Allow the existing assistant component to grow in place while trailing notification/status/editor components remain below it.
- Wait for the response and background tasks to finish.
- Scroll upward through the terminal's native scrollback.
- Inspect the point where the beginning of the assistant response should appear.
A more deterministic manual reproduction could make the assistant response begin with unique sentinels such as:
SUMMARY-SENTINEL-01
SUMMARY-SENTINEL-02
...
SUMMARY-SENTINEL-20
and then verify whether every sentinel appears exactly once in terminal scrollback.
Actual behavior
- The live viewport remains near the bottom.
- Some earlier rows of the growing assistant component are absent from native scrollback.
- Old rows from the preceding tool component remain physically present where the missing assistant rows should have appeared.
- The renderer does not later repair or emit the missing rows.
- Scrolling up cannot recover the beginning of the response.
Expected behavior
- Every finalized logical assistant row is available exactly once in either native terminal scrollback or an application-owned scrollable transcript.
- Rows that cross above the live viewport are committed to scrollback before the visible viewport is repainted.
- Previous tool output must not remain in physical terminal rows that have logically been replaced by assistant content.
- Background notifications and pinned suffix components must not cause the growing assistant message to lose its earlier rows.
Source investigation and probable cause
This diagnosis is based on static inspection of the uploaded v0.12.6 source and should be confirmed with a process-style terminal regression test.
The suspicious path is in packages/tui/src/tui.ts.
1. viewportRepaint() emits only the visible screen window
At approximately packages/tui/src/tui.ts:3389-3455, viewportRepaint():
- selects
nextViewportTop;
- clears and redraws only
height physical screen rows;
- then records the entire logical
newLines array as #previousLines.
Relevant behavior:
for (let screenRow = 0; screenRow < height; screenRow++) {
// Clear and render only nextViewportTop + screenRow.
}
this.#viewportTopRow = nextViewportTop;
this.#previousLines = newLines;
A viewport-only repaint does not, by itself, emit all logical rows that moved above the viewport into native terminal scrollback.
2. Changes above the previous viewport use viewportRepaint()
At approximately packages/tui/src/tui.ts:3707-3718:
if (firstChanged < prevViewportTop) {
if (useViewportRepaintPath(this.terminal) || ...) {
viewportRepaint(...);
return;
}
fullRender(true, ...);
}
A streaming assistant component can change above the live-following viewport as it grows and reflows.
3. Scrollback-frontier recovery is gated by appendedLines
At approximately packages/tui/src/tui.ts:3598-3627, the committed-frontier recovery path starts with:
if (
appendedLines &&
this.#scrollbackResumeViewportTop !== undefined &&
nextLiveViewportTop > prevViewportTop
) {
...
}
The reported layout may not be classified as a pure append to the complete rendered tree. The assistant component is mutable and may be followed by background-job notifications, status content, or the pinned editor/HUD. Its expansion can therefore move the viewport while changing lines in the middle of the rendered tree.
Working hypothesis
When the mutable assistant component grows before trailing components:
- its beginning crosses above the live viewport;
- the update takes a viewport-only repaint path;
- the crossed rows are not physically admitted to native scrollback;
#previousLines is nevertheless updated to the full logical transcript;
- later renders consider those rows already represented and never emit them;
- stale terminal rows from the earlier tool component remain in scrollback.
This would be the inverse of a duplicate-admission bug: a logical row is admitted zero times instead of twice.
Suggested automated regression test
Add a process-style VirtualTerminal regression with a small height.
Construct a component tree containing:
- historical/tool rows that already fill more than one viewport;
- a mutable assistant component;
- several fixed notification rows after the assistant component;
- the normal pinned status/editor suffix.
Test sequence:
- Render the assistant component with two sentinel lines.
- Grow the same component in place to at least eight or ten sentinel lines.
- Trigger renders while trailing notification rows remain after it.
- Let the viewport advance more than once.
- Inspect both the live grid and
term.getScrollBuffer().
Assertions:
expect(countMatches(term.getScrollBuffer(), /SUMMARY-SENTINEL-01/)).toBe(1);
expect(countMatches(term.getScrollBuffer(), /SUMMARY-SENTINEL-02/)).toBe(1);
// Repeat for every sentinel.
Also assert that no stale tool sentinel remains directly adjacent to the middle of the assistant response where earlier assistant sentinels should be.
The test should cover both:
- a normal process terminal that uses
useViewportRepaintPath();
- a multiplexer/host-neutral fallback path, where applicable.
Possible fix direction
Generalize native-scrollback admission tracking so it applies whenever the live viewport advances, not only when the entire rendered tree is recognized as a simple append.
Before a viewport-only repaint advances the visible top, every logical row crossing from the visible window into native scrollback should be committed exactly once—or the application should retain and expose those rows through an application-owned scrollback model.
The existing #nativeScrollbackViewportTop and #scrollbackResumeViewportTop machinery is likely the correct place to extend, but the fix should be driven by a regression test because scrollback admission is terminal-path-sensitive.
Related issues and pull requests
Strongly related scrollback history
Current open work reviewed
No other open PR at the time of review was specifically about native scrollback, streaming transcript admission, or long ask content.
Diagnostics that would help
Reproduce once with redraw debugging enabled:
Then attach the redraw log, normally:
~/.gjc/agent/gjc-debug.log
The actual path respects GJC_CONFIG_DIR and GJC_CODING_AGENT_DIR overrides.
Also include:
gjc --version
bun --version
stty size
printf 'TERM=%s\nTERM_PROGRAM=%s\nTERM_PROGRAM_VERSION=%s\nCOLORTERM=%s\n' \
"$TERM" "$TERM_PROGRAM" "$TERM_PROGRAM_VERSION" "$COLORTERM"
If a multiplexer is involved, include its version and whether the issue reproduces outside it.
Summary
When a long assistant response or final summary is rendered in the interactive TUI, the beginning of the response can disappear from the terminal UI and native scrollback.
The missing area is not blank. Rows from an earlier tool call remain visible in their place, which makes it look as though the previous tool component overwrote the first part of the assistant response.
This was observed while a final summary was streaming after a large Bash/agent-browser component and while background-job completion notifications were also being rendered.
User impact
Important context at the beginning of a final answer can become unreadable, including:
The text appears to exist in the logical assistant response, but it is not recoverable by scrolling the terminal upward.
Observed example
The first visible part of the final summary was:
There should have been additional assistant text before those lines. Instead, the rows immediately above them still showed the previous Bash component:
The stale Bash rows and the middle of the assistant summary are contiguous in terminal scrollback.
Screenshots
The first screenshot shows stale Bash/tool rows immediately above a summary that begins in the middle. The second shows the completed response and background-job notifications, with the missing earlier summary text still unavailable.
Environment
v0.12.5v0.12.6$TERM:$TERM_PROGRAM:Steps to reproduce
The original occurrence involved a complex agent run, but the following conditions appear important:
A more deterministic manual reproduction could make the assistant response begin with unique sentinels such as:
and then verify whether every sentinel appears exactly once in terminal scrollback.
Actual behavior
Expected behavior
Source investigation and probable cause
This diagnosis is based on static inspection of the uploaded
v0.12.6source and should be confirmed with a process-style terminal regression test.The suspicious path is in
packages/tui/src/tui.ts.1.
viewportRepaint()emits only the visible screen windowAt approximately
packages/tui/src/tui.ts:3389-3455,viewportRepaint():nextViewportTop;heightphysical screen rows;newLinesarray as#previousLines.Relevant behavior:
A viewport-only repaint does not, by itself, emit all logical rows that moved above the viewport into native terminal scrollback.
2. Changes above the previous viewport use
viewportRepaint()At approximately
packages/tui/src/tui.ts:3707-3718:A streaming assistant component can change above the live-following viewport as it grows and reflows.
3. Scrollback-frontier recovery is gated by
appendedLinesAt approximately
packages/tui/src/tui.ts:3598-3627, the committed-frontier recovery path starts with:The reported layout may not be classified as a pure append to the complete rendered tree. The assistant component is mutable and may be followed by background-job notifications, status content, or the pinned editor/HUD. Its expansion can therefore move the viewport while changing lines in the middle of the rendered tree.
Working hypothesis
When the mutable assistant component grows before trailing components:
#previousLinesis nevertheless updated to the full logical transcript;This would be the inverse of a duplicate-admission bug: a logical row is admitted zero times instead of twice.
Suggested automated regression test
Add a process-style
VirtualTerminalregression with a small height.Construct a component tree containing:
Test sequence:
term.getScrollBuffer().Assertions:
Also assert that no stale tool sentinel remains directly adjacent to the middle of the assistant response where earlier assistant sentinels should be.
The test should cover both:
useViewportRepaintPath();Possible fix direction
Generalize native-scrollback admission tracking so it applies whenever the live viewport advances, not only when the entire rendered tree is recognized as a simple append.
Before a viewport-only repaint advances the visible top, every logical row crossing from the visible window into native scrollback should be committed exactly once—or the application should retain and expose those rows through an application-owned scrollback model.
The existing
#nativeScrollbackViewportTopand#scrollbackResumeViewportTopmachinery is likely the correct place to extend, but the fix should be driven by a regression test because scrollback admission is terminal-path-sensitive.Related issues and pull requests
Strongly related scrollback history
#3212 — TUI duplicates transcript lines in native scrollback after streaming reflow
#3394 — TUI: duplicated rows at the viewport-top seam on 0.11.11
viewportRepaint()paints only the visible window while differential append rendering performs native-scrollback admission.#1969 — Regression: TUI jumps to top when assistant response completes on Windows-to-Linux SSH
#1793 — TUI viewport jumps to top while answers stream on Windows WSL
Current open work reviewed
PR #3658 — fix(tui): keep padded text within render width
TruncatedTextpadding exceeds allocated width.Issue #3639 — Shell-mode (
!cmd) output is not displayed while the agent is WorkingPR #3660 — fix(coding-agent): retain deferred shell output
!cmdshell/Python components across pending-message and transcript rebuilds.No other open PR at the time of review was specifically about native scrollback, streaming transcript admission, or long
askcontent.Diagnostics that would help
Reproduce once with redraw debugging enabled:
Then attach the redraw log, normally:
The actual path respects
GJC_CONFIG_DIRandGJC_CODING_AGENT_DIRoverrides.Also include:
If a multiplexer is involved, include its version and whether the issue reproduces outside it.