Skip to content

fix(tui): prevent transcript loss across reflows - #3684

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
snowykr:fix/durable-tui-runtime-hardening
Jul 31, 2026
Merged

fix(tui): prevent transcript loss across reflows#3684
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
snowykr:fix/durable-tui-runtime-hardening

Conversation

@snowykr

@snowykr snowykr commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

Fix TUI transcript clipping, disappearance, and duplicate Tool output when resize/reflow is coalesced with streaming output.

  • Baseline: bd5aea72 (origin/dev during the observed run).
  • Expected: every streamed row appears once and in order; the offscreen Tool card and bottom-pinned prompt remain intact.
  • Observed on baseline: the real terminal transcript omitted ROW 034 and ROW 124.

Reproduction

The developer-local interactive dogfood harness uses the production ProcessTerminal, TUI, ToolExecutionComponent, and bottom-pinned CustomEditor.

  1. Start the TUI and wait for the initial frame.
  2. Render a real ToolExecution card.
  3. Stream 180 numbered rows at 100 ms intervals, including a mid-stream Tool result update.
  4. Scroll through native terminal history after completion and inspect the retained Tool card and fixed prompt.

Change

  • Keep the logical rendered frame separate from the prefix durably admitted to native scrollback.
  • Coalesce resize/reflow with output only when the retained prefix is proven safe; otherwise repaint the viewport without admitting unproven rows.
  • Preserve an append-only durable baseline across temporary stop/start ownership transfers, preventing replay while admitting rows produced during the stop interval.

Verification

Interactive dogfood

The production-component terminal run displayed ROW 001 through ROW 180 exactly once and in order. It exercised the ToolExecution card and bottom-pinned input rather than a synthetic text-only renderer.

The current TUI patch is unchanged after rebasing onto upstream dev; it is byte-identical to the locally dogfooded TUI revision.

Automated

  • On this exact head: TUI focused suite — 183 passed, 0 failed, 1,780 assertions.
  • On this exact head: bun --cwd=packages/tui run check passed.
  • CI is running against this exact rebased revision.

Scope

The behavioral change is confined to TUI frame admission, reflow, repaint, and temporary ownership-transfer handling.

Upstream dev base: 1ccb5f84350ad4e3f0be286e91ded6fc39982df5
Head: 77a4b1d567c7213671045ce291a98ec4d2e38ca5

Keep resize and append boundaries durable only when the rendered prefix is proven unchanged, preserving wrapped rows, viewport state, and terminal history without replaying prior content.

Tested: bun test focused TUI regression, viewport, write-boundary, fixture, and resize-storm suites

Tested: bun x biome check focused TUI files
@snowykr
snowykr force-pushed the fix/durable-tui-runtime-hardening branch from 721f5ae to 77a4b1d Compare July 31, 2026 19:01

@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: 721f5aefb7

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/tui/src/tui.ts
Comment thread packages/tui/src/tui.ts
@Yeachan-Heo

Copy link
Copy Markdown
Owner

Hostile disposition: MERGE_READY — awaiting terminal native/viewport checks

Exact head 77a4b1d567c7213671045ce291a98ec4d2e38ca5; exact base is current dev 1ccb5f8 as requested. The one-commit change directly covers issue #3674’s streaming transcript/native-scrollback loss surface: it keeps the latest logical frame and durable transcript/viewport state across coalesced resize/reflow, adds deterministic render-golden fixtures, replay/resize/viewport regressions, and documents the real-terminal/multiplexer viewport policy. It does not overlap #3665’s NotificationServer callback ownership or #3675’s ordinary ask premise backlog; #3680’s #3674 lane can be retired/superseded after this PR’s merge while preserving #3675.

Hostile verification: focused exact-head TUI suites passed 120 tests with 858 assertions locally, including viewport scroll, render regressions, resize replay storms, width settling, render commits, and render goldens. Fixture churn is deterministic and tied to explicit render-golden cases; no unexplained broad golden rewrite was found. The implementation preserves native scrollback by avoiding clear/replay for real process terminals and marked multiplexers, while retaining full redraw for headless terminals and explicit legacy override behavior. The remaining hosted checks are not red: native-build and viewport/resize focused checks are pending. No P0/P1 findings.

Merge is authorized only after the exact current-head CI becomes terminal green; no merge was performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo merged commit eacf534 into Yeachan-Heo:dev Jul 31, 2026
28 checks passed
Yeachan-Heo pushed a commit that referenced this pull request Aug 1, 2026
The #3684 chain removed the shouldUseViewportRepaintForHost gate from
the width-change viewport-repaint intercept, making ALL terminals
unconditionally redirect width-changed renders to a live viewport
repaint. On plain terminals (non-multiplexer, non-process-terminal),
this bypassed fullRender which properly clears scrollback and replays
the full transcript. Korean/CJK prose wrapping broke at narrow widths
because the viewport repaint only painted visible rows without
committing the full frame — the suffix was split across viewport
boundaries with selector content interleaved between its halves.

Restored the gate: compute useViewportRepaintPath before the intercept
and only redirect to viewportRepaint when the host is actually a
viewport-repaint host. Also restored the fullRender fallback for the
firstChanged < viewportTop branch and propagated IME cursor write
failure from #writeRenderBufferAndReanchorImeCursor so callers detect
terminal detach.

Updated affected #3684-added tests to use isProcessTerminal: true on
their VirtualTerminals where they test viewport-repaint-specific
behavior, and regenerated render golden fixtures.

Issue: #1979
Confidence: high
Scope-risk: narrow
Reversibility: revert-commit
Tested: full TUI suite + issue-1979 Korean wrap test
Not-tested: real multiplexer session (CI covers via env injection)
Yeachan-Heo pushed a commit that referenced this pull request Aug 1, 2026
The #3684 chain removed the shouldUseViewportRepaintForHost gate from
the width-change viewport-repaint intercept, making ALL terminals
unconditionally redirect width-changed renders to a live viewport
repaint. On plain terminals (non-multiplexer, non-process-terminal),
this bypassed fullRender which properly clears scrollback and replays
the full transcript. Korean/CJK prose wrapping broke at narrow widths
because the viewport repaint only painted visible rows without
committing the full frame — the suffix was split across viewport
boundaries with selector content interleaved between its halves.

Restored the gate: compute useViewportRepaintPath before the intercept
and only redirect to viewportRepaint when the host is actually a
viewport-repaint host. Also restored the fullRender fallback for the
firstChanged < viewportTop branch and propagated IME cursor write
failure from #writeRenderBufferAndReanchorImeCursor so callers detect
terminal detach.

Updated affected #3684-added tests to use isProcessTerminal: true on
their VirtualTerminals where they test viewport-repaint-specific
behavior, and regenerated render golden fixtures.

Issue: #1979
Confidence: high
Scope-risk: narrow
Reversibility: revert-commit
Tested: full TUI suite + issue-1979 Korean wrap test
Not-tested: real multiplexer session (CI covers via env injection)
Yeachan-Heo pushed a commit that referenced this pull request Aug 1, 2026
The #3684 chain removed the shouldUseViewportRepaintForHost gate from
the width-change viewport-repaint intercept, making ALL terminals
unconditionally redirect width-changed renders to a live viewport
repaint. On plain terminals (non-multiplexer, non-process-terminal),
this bypassed fullRender which properly clears scrollback and replays
the full transcript. Korean/CJK prose wrapping broke at narrow widths
because the viewport repaint only painted visible rows without
committing the full frame — the suffix was split across viewport
boundaries with selector content interleaved between its halves.

Restored the gate: compute useViewportRepaintPath before the intercept
and only redirect to viewportRepaint when the host is actually a
viewport-repaint host. Also restored the fullRender fallback for the
firstChanged < viewportTop branch and propagated IME cursor write
failure from #writeRenderBufferAndReanchorImeCursor so callers detect
terminal detach.

Updated affected #3684-added tests to use isProcessTerminal: true on
their VirtualTerminals where they test viewport-repaint-specific
behavior, and regenerated render golden fixtures.

Issue: #1979
Confidence: high
Scope-risk: narrow
Reversibility: revert-commit
Tested: full TUI suite + issue-1979 Korean wrap test
Not-tested: real multiplexer session (CI covers via env injection)
Yeachan-Heo added a commit that referenced this pull request Aug 1, 2026
#3691)

The #3684 chain removed the shouldUseViewportRepaintForHost gate from
the width-change viewport-repaint intercept, making ALL terminals
unconditionally redirect width-changed renders to a live viewport
repaint. On plain terminals (non-multiplexer, non-process-terminal),
this bypassed fullRender which properly clears scrollback and replays
the full transcript. Korean/CJK prose wrapping broke at narrow widths
because the viewport repaint only painted visible rows without
committing the full frame — the suffix was split across viewport
boundaries with selector content interleaved between its halves.

Restored the gate: compute useViewportRepaintPath before the intercept
and only redirect to viewportRepaint when the host is actually a
viewport-repaint host. Also restored the fullRender fallback for the
firstChanged < viewportTop branch and propagated IME cursor write
failure from #writeRenderBufferAndReanchorImeCursor so callers detect
terminal detach.

Updated affected #3684-added tests to use isProcessTerminal: true on
their VirtualTerminals where they test viewport-repaint-specific
behavior, and regenerated render golden fixtures.

Issue: #1979
Confidence: high
Scope-risk: narrow
Reversibility: revert-commit
Tested: full TUI suite + issue-1979 Korean wrap test
Not-tested: real multiplexer session (CI covers via env injection)

Co-authored-by: gaebal-gajae <gaebal-gajae@users.noreply.github.com>
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.

2 participants