Skip to content

Commit 1e136bf

Browse files
authored
fix(tui): stabilize native history and diff overlay frames (#390)
* fix(tui): prevent idle transcript replay and diff overlay frames * fix(tui): retain prompt identity and deferred main-buffer geometry
1 parent 9b9c07b commit 1e136bf

12 files changed

Lines changed: 777 additions & 102 deletions

File tree

‎docs/tui-capabilities.md‎

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,27 @@ The live application view remains the source for the current transcript.
1212
Long turns keep a bounded live projection. Dropping its oldest cells does not replay
1313
the welcome banner or erase earlier output. When activity or Todo panels shrink,
1414
released rows temporarily remain blank between the transcript and footer; new
15-
output consumes that space. Tables and code blocks remain contiguous.
15+
output consumes that space. Tables and code blocks remain contiguous. Short
16+
conversations also anchor the welcome rows, so background status changes and
17+
footer completion do not replay the banner or duplicate existing warnings.
18+
Accepting a submitted message retains its emitted row identity, so the first
19+
prompt does not introduce a refresh boundary or duplicate a long message.
1620

1721
When an unrelated transcript replaces the current projection, a labelled refresh
1822
boundary separates the retained output from the new document. Resize and forced
19-
redraw preserve pending output and native history. Explicit resume-start history
23+
redraw preserve pending output and native history, including consecutive resizes
24+
while output is held or fullscreen mode is active. Mode changes preserve the main
25+
state after its final pending frame is flushed. Explicit resume-start history
2026
clearing remains a separate startup policy. Native image history is retained until
2127
the terminal itself evicts it.
2228

2329
Transient overlays use an alternate buffer so opening, nesting or resizing panels
2430
cannot place menu rows in native history. Closing the last overlay restores the
2531
main buffer and reconciles background output; terminal shutdown also restores it.
32+
Unchanged overlay frames produce no terminal output. Text changes update only
33+
affected rows, while cursor changes preserve IME positioning and visibility.
34+
Forced redraws and geometry changes invalidate that frame cache; image changes
35+
retain full-frame repaint.
2636

2737
## Output speed
2838

@@ -224,25 +234,19 @@ area, including short Rewind previews and scope pickers. Closing a panel restore
224234
the current conversation. Closing, replacing or shrinking a transient region
225235
restores the exposed chat rows. This includes inline selectors such as `/theme`,
226236
completion menus, multi-line drafts, image previews, queued messages, task and
227-
Goal summaries, welcome notices and status rows. Short documents refresh in place;
228-
history is reconstructed only when the smaller layout needs to bring scrolled
229-
rows back into view. This rule follows the rendered layout, including asynchronous
230-
updates, rather than requiring each close handler to request a special redraw.
231-
When background running content shrinks entirely within the current screen and
232-
the transient layout stays unchanged, the renderer keeps native scrollback and
233-
the Composer position stable.
234-
Freed rows temporarily remain blank at the top of the active screen and subsequent
235-
output reuses them. This avoids resetting the host's scroll position when a turn
236-
finishes. Redundant resize notifications with unchanged dimensions do not rebuild
237-
history. Viewport-only redraws erase rows in place so terminals that save a cleared
238-
screen to scrollback, including Apple Terminal, do not retain the old Composer,
239-
status line or duplicate transcript rows.
240-
241-
When a change removes or replaces text already in scrollback, the renderer still
242-
reconstructs the current session to avoid stale or duplicate history. Real resizes
243-
and image layout changes also retain the existing reconstruction behavior. A
244-
reconstruction clears earlier shell scrollback and can reset the host's scroll
245-
position; ordinary updates keep native scrolling and selection behavior.
237+
Goal summaries, welcome notices and status rows. Short documents refresh in place.
238+
When background content shrinks, released rows remain between the document body
239+
and footer, preserving the Composer position and native scroll history. Subsequent
240+
output reuses that space. Welcome rows participate in this alignment even before
241+
any transcript row has reached history, so asynchronous connection changes and
242+
footer completion do not start a new transcript snapshot.
243+
244+
Redundant resize notifications with unchanged dimensions do not rebuild history.
245+
Viewport redraws erase addressable rows in place. The immutable-history and
246+
labelled-snapshot rules in [Regular-mode terminal history](#regular-mode-terminal-history)
247+
also apply when a document is replaced or cannot be aligned after reflow. Already
248+
emitted native rows keep their historical text and wrapping; redraws retain shell
249+
history rather than clearing it.
246250

247251
Rewind and Fork history-loading hints disappear as soon as their lists are ready.
248252
Returning from a cancelled operation must not leave a stale loading message in the

‎packages/tui/src/tui/controller/chat-controller.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ export class TuiChatController {
372372
if (!isRetryContinuation) {
373373
this.transcript.upsert({
374374
id: `user:${turnId}`,
375+
...(optimisticCell ? { scrollbackId: optimisticCell.scrollbackId ?? optimisticCell.id } : {}),
375376
kind: 'user',
376377
status: 'pending',
377378
content: formatTuiSubmission(displayContent, attachments),

‎packages/tui/src/tui/engine/LOCAL_CHANGES.json‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
},
164164
{
165165
"path": "tui-main-screen.ts",
166-
"currentSha256": "0095b75384b9f99cc979e55c5ef40a94c58d79ea9e9394ad978ad61f0c2db3d8",
166+
"currentSha256": "e7458ff5c3123e89883bcc3c10a21694e8949d75bb483ca98a208693fb3162b4",
167167
"changeIds": [
168168
"L005",
169169
"L017",
@@ -175,15 +175,17 @@
175175
"L039",
176176
"L041",
177177
"L044",
178-
"L045"
178+
"L045",
179+
"L046",
180+
"L047"
179181
],
180182
"upstreamCommit": "6c4f360264397c59801f6da2bdac13e3b1fcbe91",
181-
"reason": "Align bounded regular-mode projections with emitted row identities and preserve native history across folding, footer shrink, redraw and resize.",
182-
"behaviorImpact": "Native scrollback retains emitted snapshots and pre-launch shell history. Automatic folding and footer updates preserve scrolling; unmatched projections archive the visible body and start a labelled snapshot. Resize retains pending rows, forced redraw retains physical state, and historical image resources survive viewport repaints. Transient overlays use a separate terminal buffer and return to main on close or stop, preserving main geometry across resize."
183+
"reason": "Align bounded regular-mode projections with emitted row identities and preserve native history across folding, footer shrink, redraw and resize. Diff isolated overlay frames and align short conversations at welcome rows. Preserve admitted row identity and observe deferred or inactive main-buffer resizes.",
184+
"behaviorImpact": "Native scrollback retains emitted snapshots and pre-launch shell history. Automatic folding and footer updates preserve scrolling; unmatched projections archive the visible body and start a labelled snapshot. Resize retains pending rows, forced redraw retains physical state, and historical image resources survive viewport repaints. Transient overlays use a separate terminal buffer and return to main on close or stop, preserving main geometry across resize. Unchanged overlays emit no frame bytes; text changes repaint only affected rows. Background welcome and footer updates retain short-conversation history without replaying the banner or warnings. First submission does not replay existing output. Every physical resize is retained through backpressure and fullscreen ownership; short frames that still fit avoid a snapshot marker."
183185
},
184186
{
185187
"path": "tui.ts",
186-
"currentSha256": "03ef4b80122f127860dddf6c4ff51643f4b7ec47c8ce18d0e1855d27ce77ec57",
188+
"currentSha256": "660739ee74e61ba1e4deadb6f026ba116d080c03e6d350ff0230f7a838d4809a",
187189
"changeIds": [
188190
"L005",
189191
"L015",
@@ -194,10 +196,12 @@
194196
"L040",
195197
"L041",
196198
"L044",
197-
"L045"
199+
"L045",
200+
"L046",
201+
"L047"
198202
],
199-
"reason": "Expose bounded row anchors, source-block retention and the transcript/footer boundary for regular-mode output snapshots.",
200-
"behaviorImpact": "Regular renderers can distinguish projection eviction from retained-block presentation changes."
203+
"reason": "Expose bounded row anchors, source-block retention and the transcript/footer boundary for regular-mode output snapshots. Distinguish prelude anchors and expose physical cursor hiding to cached renderers. Publish layout padding and physical resize observations for inactive main state.",
204+
"behaviorImpact": "Regular renderers can distinguish projection eviction from retained-block presentation changes. Prelude rows cannot mask transcript replacement; nested overlays and coalesced cursor preference changes restore cursor visibility. Owners observe physical geometry before render scheduling, and row anchors can ignore only layout-owned leading padding."
201205
},
202206
{
203207
"path": "utils.ts",

‎packages/tui/src/tui/engine/LOCAL_CHANGES.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,23 @@ Remove `L024` when the selected Pi baseline natively matches legacy-terminal `Ct
215215
- Evidence: engine and application regressions verify host scroll position, unique emitted rows, Todo completion, grouped reads and long-turn/turn-window eviction, contiguous tables, transient panels, cursor placement, bounded caches, oversized footers, resize bursts, image-resource lifetime, forced refresh, and mode-state restore under xterm and an ED 2 clear-to-scrollback model.
216216
- Boundary: old native rows retain their historical text and wrapping. Explicit resume-start clearing is unchanged. Native remote-host and cross-platform acceptance remain separate from local emulator verification.
217217
- Removal condition: the selected upstream engine supports immutable native output with bounded anchored live projections.
218+
219+
220+
## L046: Avoid idle overlay output and short-conversation replay
221+
222+
- Product contract: unchanged isolated overlays emit no frame bytes. Changed text rows repaint in place; cursor-only movement still updates IME position. Short conversations retain native scroll position when background connection/account status or footer height changes while the welcome occupies history.
223+
- Minimal difference: MainScreen caches one normalized overlay frame and its physical cursor visibility. Entry, exit, forced refresh, restore and each geometry transition invalidate the cache; image changes retain full-frame repaint. Base cursor-hiding control paths update the visibility cache so nested panels and coalesced preference changes restore the cursor.
224+
- Short conversations: ChatLayout publishes welcome row anchors in document mode. With block metadata, prelude anchors have no block identity and are excluded from transcript-replacement detection. Shared welcome rows cannot hide a new session or projection eviction. The existing immutable-history alignment and body/footer padding apply before the first transcript row reaches native history.
225+
- Evidence: `tui-scrollback-stability.test.ts` covers idle/one-row overlay output, physical cursor changes, force refresh, resize roundtrips, close/reopen, short-conversation background state and footer changes, unique warning emission, and real transcript replacement. Independent emulator checks cover account notice height changes, image fallback and lifecycle boundaries.
226+
- Boundary: retained native welcome rows keep their emitted status; the current visible welcome and footer render current state. This does not change content-review decisions, runtime warning events or native-host guarantees.
227+
- Removal condition: upstream provides differential isolated overlays and prelude-aware native-history alignment.
228+
229+
230+
## L047: Preserve output identity and deferred main-buffer geometry
231+
232+
- Product contract: admitting an optimistic user message keeps its emitted history identity, including long prompts. Consecutive resizes preserve every output row even while writes are held or the fullscreen renderer owns the terminal. Switching modes snapshots main state after any final resize flush.
233+
- Minimal difference: MainScreen tracks physical geometry separately from the last rendered frame and exposes the same resize calculation for an inactive saved main state. The base renderer forwards physical resize events to its owner before scheduling frames. Reflow that still fits on screen repaints in place; only potentially scrolled reflow needs an archived snapshot. Row alignment ignores declared layout padding and recognizes retained row identities when a source block id changes.
234+
- Product integration: the transcript keeps canonical store ids while carrying an optional emitted-row identity across admission. ChatLayout publishes its outer padding. The interactive renderer observes inactive main-buffer resizes and captures state after stop has flushed pending output.
235+
- Evidence: actual application submission tests cover three terminal sizes and a 120-line prompt. Scrollback tests cover five held-output resize sequences, three fullscreen resize sequences and a mode switch with a pending resized frame. Existing IME tests assert cursor position at synchronized frame boundaries after short-document width changes; overlay reflow tests retain overflowed history.
236+
- Boundary: resize tracking models the existing local emulator host behavior. Native remote-host and cross-platform acceptance remain separate; genuinely unalignable history still starts a labelled snapshot.
237+
- Removal condition: upstream retains emitted row identity through admission and tracks physical main-buffer geometry across deferred frames and renderer ownership changes.

0 commit comments

Comments
 (0)