@oh-my-pi/pi-tui 15.10.9 → 15.10.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +5 -5
- package/dist/types/components/box.d.ts +1 -1
- package/dist/types/components/editor.d.ts +1 -1
- package/dist/types/components/image.d.ts +1 -1
- package/dist/types/components/input.d.ts +1 -1
- package/dist/types/components/loader.d.ts +1 -1
- package/dist/types/components/markdown.d.ts +5 -1
- package/dist/types/components/scroll-view.d.ts +1 -1
- package/dist/types/components/select-list.d.ts +1 -1
- package/dist/types/components/settings-list.d.ts +9 -6
- package/dist/types/components/spacer.d.ts +1 -1
- package/dist/types/components/tab-bar.d.ts +1 -1
- package/dist/types/components/text.d.ts +1 -1
- package/dist/types/components/truncated-text.d.ts +1 -1
- package/dist/types/kill-ring.d.ts +0 -7
- package/dist/types/stdin-buffer.d.ts +11 -0
- package/dist/types/terminal-capabilities.d.ts +1 -44
- package/dist/types/terminal.d.ts +0 -36
- package/dist/types/tui.d.ts +76 -72
- package/package.json +3 -3
- package/src/components/box.ts +43 -63
- package/src/components/editor.ts +187 -49
- package/src/components/image.ts +29 -5
- package/src/components/input.ts +1 -1
- package/src/components/loader.ts +1 -1
- package/src/components/markdown.ts +68 -50
- package/src/components/scroll-view.ts +1 -1
- package/src/components/select-list.ts +1 -1
- package/src/components/settings-list.ts +150 -26
- package/src/components/spacer.ts +9 -5
- package/src/components/tab-bar.ts +1 -1
- package/src/components/text.ts +1 -1
- package/src/components/truncated-text.ts +10 -2
- package/src/kill-ring.ts +5 -0
- package/src/stdin-buffer.ts +103 -27
- package/src/terminal-capabilities.ts +5 -150
- package/src/terminal.ts +99 -40
- package/src/tui.ts +802 -1827
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.10.11] - 2026-06-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `SettingsList` now supports type-to-search filtering with Escape clearing an active query before canceling.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Preserved list selection by item ID when replacing settings so focus stays on the same setting
|
|
14
|
+
- Displayed a no matching settings message and search-editing hint when filtering returns no matches
|
|
15
|
+
- Expanded settings search matching to include IDs, current values, descriptions, and option values as well as labels
|
|
16
|
+
- Raised the stdin split-escape flush window from 10ms to 50ms: over laggy links (ssh, slow multiplexers) a CSI sequence split across reads was flushed as literal data, leaking `[` + `A` style fragments into the editor as typed text
|
|
17
|
+
- Lengthened the OSC 11 appearance poll on terminals without Mode 2031 from 2s to 30s — each poll's query write cleared the user's active text selection, breaking copy every two seconds on Alacritty/Warp/older WezTerm
|
|
18
|
+
- Rewrote `StdinBuffer.extractCompleteSequences` to index-based scanning: the previous per-iteration `slice` + `Array.from(remaining)[0]` made plain-text bursts O(n²), turning a 100KB non-bracketed paste into a multi-second freeze
|
|
19
|
+
- Capped the editor undo stack at 100 entries with word-level coalescing of consecutive single-character inserts (matching `Input`), capped the kill ring at 60 entries, cached word-wrap layout per (line, width) so each render and key handler shares one wrap pass, and batched ≤1000-char single-line pastes into one insert + one trigger-detection pass instead of per-character replay
|
|
20
|
+
- Virtualized the frame pipeline around a stable-prefix contract — the renderer no longer does O(total transcript) work per frame. `Component.render` now returns `readonly string[]`: results are component-owned, callers must not mutate them, and an unchanged component returns the same array reference (reference equality proves byte-identical rows). `Container.render` memoizes its concatenation on child references (children are still rendered every frame for their side effects); `Box` replaced its content-hashing cache with the same child-reference memo (no more per-frame `leftPad + line` rebuilds and full-content hashing); `Markdown`, `Spacer`, and `TruncatedText` return their cached arrays by reference instead of defensive copies. The TUI composes a persistent frame from per-child segments and an opt-in `RenderStablePrefix` report (consumable floor semantics for in-place mutators like the transcript), so marker extraction, line preparation (persistent prepared-frame replacing the per-frame rebuilt cache arrays), and the committed-prefix audit now run only over rows at/after the first changed row instead of every line of the transcript every frame
|
|
21
|
+
- Rewrote the render core around an append-only native-scrollback contract. Committed rows are immutable: rows enter terminal history exactly once, in order, when the component-reported commit boundary (`NativeScrollbackLiveRegion`) marks them final, and the visible window repaints in place with relative moves. The engine no longer probes the terminal's scroll position or guesses whether a destructive rebuild is safe — the entire ED3-risk/defer/checkpoint machinery (viewport probes, eager streaming mode, dirty-scrollback reconciliation, deferred shrink/mutation intents, streaming high-water rebuilds, ConPTY-specific defer paths) is deleted. ED3 (`CSI 3 J`) now fires only on explicit user gestures: session replace, resize outside multiplexers, and `resetDisplay()`. This structurally removes the yank / flash / duplicated-rows / invisible-until-resize failure families tracked across #1610, #1635, #1651, #1682, #1719, #1746, #1799, #1823, #1962, #1974, #2000, #2011, #2154.
|
|
22
|
+
- A frame that shrinks into its committed prefix re-anchors the visible window at the new tail and restarts commit bookkeeping; previously committed rows stay in history (history is never rewritten without a gesture).
|
|
23
|
+
- Overlays now composite into the visible window slice only and freeze commits while visible, so overlay pixels can never enter native scrollback and closing an overlay no longer triggers a destructive history rebuild.
|
|
24
|
+
- Inline-image budget demotion now deletes the demoted image's graphics by id and lets the window diff repaint the text fallback — no more mid-session destructive full replay when the image cap is exceeded.
|
|
25
|
+
- The render-stress harness now validates the contract with a shadow commit ledger (an independent reimplementation of the ledger math fed only by observed frames and bytes), asserting scrollback equals the committed prefix row-for-row and that tape growth matches physical scroll exactly, across randomized op sequences, resizes, overlays, and multiplexer scenarios. The ghostty-web virtual terminal additionally survives libghostty-vt 0.4's WASM allocator traps via an event-log replay/compaction recovery, and strips non-spacing combining marks on input (a margin-aligned combining cluster deterministically corrupts that engine; mark placement through it was already unverifiable).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed Windows rendering degrading into CP437 mojibake (`Γöé`/`ΓöÇ` instead of box-drawing borders and Nerd Font glyphs) after a console-sharing child process changed the console codepage (e.g. PHP CLI's implicit `chcp`, php.net request #73716): the breakage stayed latent until the next full repaint such as ctrl+o expand. The terminal now re-asserts the UTF-8 codepage (output and input) before each stdout write
|
|
30
|
+
- Fixed crash recovery leaving the shell unusable: `emergencyTerminalRestore` (and `terminal.stop()`) never left the alt screen nor disabled mouse tracking, so a crash during a fullscreen overlay stranded the user on the alternate buffer with any-motion mouse reporting spewing escape garbage until a manual `reset`
|
|
31
|
+
- Fixed bracketed paste with a lost `ESC[201~` end marker (ssh/tmux truncation) silently eating all subsequent input forever while growing memory unboundedly — paste mode now has an inactivity watchdog (1s) and a byte cap (64 MiB) that exit paste mode and deliver the accumulated bytes through the paste event
|
|
32
|
+
- Fixed vertical cursor movement using UTF-16 code units as visual columns: Up/Down over emoji/CJK lines could land the cursor mid-surrogate-pair, rendering a lone surrogate and permanently corrupting the buffer on the next insert; movement now walks graphemes and snaps the target offset to a cluster boundary, also fixing column drift across wide glyphs
|
|
33
|
+
- Fixed cursor positions inside whitespace trimmed at a word-wrap boundary mapping to no layout line — the cursor vanished and the viewport jumped to the buffer's last line; the preceding chunk now owns the skipped whitespace run
|
|
34
|
+
- Fixed word-delete and kill-to-line operations (Ctrl+W/Alt+D/Ctrl+U/Ctrl+K) cutting through atomic paste markers, leaving `[Paste #1, +30` junk that no longer expanded to the pasted content on submit — delete ranges now extend over any atomic token they intersect
|
|
35
|
+
- Fixed the kitty CSI-u printable dedup swallowing a real keystroke arbitrarily long after the duplicated event; the pending codepoint now expires after 25ms
|
|
36
|
+
- Fixed `resetDisplay()` being a no-op on the alt screen: the redraw gesture could not repair a corrupted fullscreen modal because `#emitAltFrame` skipped identical-string repaints without consulting the force-repaint flag
|
|
37
|
+
- Fixed the ghostty initial-image paint deferral consuming resize/cursor state before abandoning the frame, which could misclassify the deferred render's reflow and corrupt the paint — the deferral check now runs before any frame state is touched
|
|
38
|
+
- Fixed the terminal-cursor inline-hint branch adding the full hint width to the line accounting even though the rendered hint was truncated, misaligning right padding whenever the hint overflowed
|
|
39
|
+
- Fixed nested markdown list detection sniffing for hardcoded `\x1b[36m` (chalk cyan): every shipped theme emits truecolor/256-color SGR for bullets, so nested items doubled their indentation per level on all real themes; nesting is now tagged structurally by the list renderer. Ordered-list continuation lines also hang by the actual bullet width, so wrapped text under `10.`+ items aligns
|
|
40
|
+
- Fixed committed transcript rows silently vanishing when a component re-laid-out content the engine had already scrolled into native history — a TTSR stream rewind truncating a streamed block, or the image budget demoting a committed inline image to its one-line fallback, shifted every row below by the height delta and the engine kept committing from the stale index, skipping that many rows of everything after (missing interruption banners, half-cut images in scrollback). The engine now audits its committed prefix every ordinary frame: an in-place edit or restyle keeps its alignment (stale styling in history remains the accepted artifact), while any shift re-anchors the commit index at the first moved row and recommits from there — history keeps the stale copy and gains a fresh one. Duplication, never loss. The detector (`findCommittedPrefixResync`, exported for the stress harness's shadow ledger) samples the prefix tail SGR-stripped so theme restyles and single-row edits never trigger spurious recommits.
|
|
41
|
+
- Fixed budget-demoted inline images shrinking their transcript block: the text fallback is now height-preserving once a graphic has rendered (reserved rows plus the fallback line), so demotion never shifts content below a committed image.
|
|
42
|
+
- Fixed stale trailing cells bleeding into committed history on combining-heavy rows: the native width model can over-count Arabic/combining clusters, classifying a short-rendering row as full-width and skipping the trailing erase — the previous occupant's cells then scrolled into scrollback baked into the committed row. Non-ASCII row rewrites now erase the line before writing.
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
|
|
46
|
+
- Removed the probe/defer API surface: `TUI.setEagerNativeScrollbackRebuild()`, `TUI.refreshNativeScrollbackIfDirty()`, `TUI.setClearOnShrink()`/`getClearOnShrink()`, `RenderRequestOptions.allowUnknownViewportMutation`, `NativeScrollbackRefreshOptions`, `Terminal.isNativeViewportAtBottom()`, `Terminal.hasEagerEraseScrollbackRisk()`, and the `eagerEraseScrollbackRisk`/`submitPinsViewportToTail` capability fields with their detectors.
|
|
47
|
+
- Removed the `PI_TUI_ED3_SAFE`, `PI_CLEAR_ON_SHRINK`, and `PI_TUI_DEBUG` environment variables (the levers they tuned no longer exist; `PI_DEBUG_REDRAW` now logs the commit-ledger state per frame).
|
|
48
|
+
|
|
5
49
|
## [15.10.9] - 2026-06-09
|
|
6
50
|
|
|
7
51
|
### Added
|
|
@@ -23,6 +67,7 @@
|
|
|
23
67
|
### Added
|
|
24
68
|
|
|
25
69
|
- Added `TUI.getFocused()` accessor and `Input.pasteText(text)` method so callers consuming non-bracketed paste transports (e.g. kitty's OSC 5522 enhanced clipboard) can route a paste payload to the currently focused modal Input rather than always to the primary editor. Mirrors the existing `Editor.pasteText` semantics: newlines stripped, tabs normalized, NFC normalization applied. ([#2127](https://github.com/can1357/oh-my-pi/issues/2127))
|
|
70
|
+
|
|
26
71
|
### Fixed
|
|
27
72
|
|
|
28
73
|
- Fixed tmux/screen/zellij rewind/branch (`requestRender(true, { clearScrollback: true })`) permanently anchoring the input box to the pane top and overlaying scrollback after a streamed reply had grown past the viewport. `#emitFullPaint` only reset `#scrollbackHighWater` inside the `clearScrollback` branch and otherwise raised it monotonically, so inside multiplexers (where `\x1b[3J` is a no-op and `clearScrollback` is forced off) the streaming peak survived the rewind; on the next frame `#planLiveRegionPinnedRender` saw the stale high-water and anchored `renderViewportTop` past the actual content, repainting every visible row blank and parking the cursor at screen row 0 for the rest of the session. A full repaint with `clearViewport: true` re-emits the entire transcript from row 0, so `#scrollbackHighWater` is now assigned (not max-clamped) to the natural push count regardless of whether ED 3 was issued ([#2130](https://github.com/can1357/oh-my-pi/issues/2130)).
|
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ All components implement:
|
|
|
62
62
|
|
|
63
63
|
```typescript
|
|
64
64
|
interface Component {
|
|
65
|
-
render(width: number): string[];
|
|
65
|
+
render(width: number): readonly string[];
|
|
66
66
|
handleInput?(data: string): void;
|
|
67
67
|
invalidate?(): void;
|
|
68
68
|
}
|
|
@@ -70,7 +70,7 @@ interface Component {
|
|
|
70
70
|
|
|
71
71
|
| Method | Description |
|
|
72
72
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
73
|
-
| `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. |
|
|
73
|
+
| `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. The result is component-owned and immutable to callers; return the same array reference when unchanged (enables renderer memoization) and a new array when content changed. |
|
|
74
74
|
| `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
|
|
75
75
|
| `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
|
|
76
76
|
|
|
@@ -590,7 +590,7 @@ class MyInteractiveComponent implements Component {
|
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
render(width: number): string[] {
|
|
593
|
+
render(width: number): readonly string[] {
|
|
594
594
|
return this.items.map((item, i) => {
|
|
595
595
|
const prefix = i === this.selectedIndex ? "> " : " ";
|
|
596
596
|
return truncateToWidth(prefix + item, width);
|
|
@@ -614,7 +614,7 @@ class MyComponent implements Component {
|
|
|
614
614
|
this.text = text;
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
-
render(width: number): string[] {
|
|
617
|
+
render(width: number): readonly string[] {
|
|
618
618
|
// Option 1: Truncate long lines
|
|
619
619
|
return [truncateToWidth(this.text, width)];
|
|
620
620
|
|
|
@@ -656,7 +656,7 @@ class CachedComponent implements Component {
|
|
|
656
656
|
private cachedWidth?: number;
|
|
657
657
|
private cachedLines?: string[];
|
|
658
658
|
|
|
659
|
-
render(width: number): string[] {
|
|
659
|
+
render(width: number): readonly string[] {
|
|
660
660
|
if (this.cachedLines && this.cachedWidth === width) {
|
|
661
661
|
return this.cachedLines;
|
|
662
662
|
}
|
|
@@ -81,7 +81,7 @@ export declare class Editor implements Component, Focusable {
|
|
|
81
81
|
*/
|
|
82
82
|
addToHistory(text: string): void;
|
|
83
83
|
invalidate(): void;
|
|
84
|
-
render(width: number): string[];
|
|
84
|
+
render(width: number): readonly string[];
|
|
85
85
|
handleInput(data: string): void;
|
|
86
86
|
getText(): string;
|
|
87
87
|
/**
|
|
@@ -82,5 +82,5 @@ export declare class Image implements Component {
|
|
|
82
82
|
#private;
|
|
83
83
|
constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
|
|
84
84
|
invalidate(): void;
|
|
85
|
-
render(width: number): string[];
|
|
85
|
+
render(width: number): readonly string[];
|
|
86
86
|
}
|
|
@@ -17,5 +17,5 @@ export declare class Input implements Component, Focusable {
|
|
|
17
17
|
* (e.g. kitty's OSC 5522 enhanced clipboard read). Mirrors `Editor.pasteText`. */
|
|
18
18
|
pasteText(text: string): void;
|
|
19
19
|
invalidate(): void;
|
|
20
|
-
render(width: number): string[];
|
|
20
|
+
render(width: number): readonly string[];
|
|
21
21
|
}
|
|
@@ -10,7 +10,7 @@ export declare class Loader extends Text {
|
|
|
10
10
|
private messageColorFn;
|
|
11
11
|
private message;
|
|
12
12
|
constructor(ui: TUI, spinnerColorFn: ColorFn, messageColorFn: LoaderMessageColorFn, message?: string, spinnerFrames?: string[]);
|
|
13
|
-
render(width: number): string[];
|
|
13
|
+
render(width: number): readonly string[];
|
|
14
14
|
start(): void;
|
|
15
15
|
stop(): void;
|
|
16
16
|
/** Lifecycle teardown: stop the animation timer. Idempotent. */
|
|
@@ -49,10 +49,14 @@ export interface MarkdownTheme {
|
|
|
49
49
|
}
|
|
50
50
|
export declare class Markdown implements Component {
|
|
51
51
|
#private;
|
|
52
|
+
/** When true, skip the module-level LRU (lookup and insert) for this instance's
|
|
53
|
+
* renders. Set for in-flight streaming partials whose text changes every frame —
|
|
54
|
+
* caching those churns the LRU with near-duplicate full-message snapshots. */
|
|
55
|
+
transientRenderCache: boolean;
|
|
52
56
|
constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
|
|
53
57
|
setText(text: string): void;
|
|
54
58
|
invalidate(): void;
|
|
55
|
-
render(width: number): string[];
|
|
59
|
+
render(width: number): readonly string[];
|
|
56
60
|
}
|
|
57
61
|
/**
|
|
58
62
|
* Render inline markdown (bold, italic, code, links, strikethrough) to a styled string.
|
|
@@ -50,7 +50,7 @@ export declare class SelectList implements Component {
|
|
|
50
50
|
setFilter(filter: string): void;
|
|
51
51
|
setSelectedIndex(index: number): void;
|
|
52
52
|
invalidate(): void;
|
|
53
|
-
render(width: number): string[];
|
|
53
|
+
render(width: number): readonly string[];
|
|
54
54
|
handleInput(keyData: string): void;
|
|
55
55
|
getSelectedItem(): SelectItem | null;
|
|
56
56
|
}
|
|
@@ -25,17 +25,20 @@ export interface SettingsListTheme {
|
|
|
25
25
|
export declare class SettingsList implements Component {
|
|
26
26
|
#private;
|
|
27
27
|
constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void);
|
|
28
|
+
getSearchQuery(): string;
|
|
29
|
+
hasSearchQuery(): boolean;
|
|
30
|
+
clearSearch(): void;
|
|
28
31
|
/** Update an item's currentValue */
|
|
29
32
|
updateValue(id: string, newValue: string): void;
|
|
30
33
|
/**
|
|
31
|
-
* Replace the entire items array. Selection is preserved
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* is
|
|
35
|
-
*
|
|
34
|
+
* Replace the entire items array. Selection is preserved by item id when
|
|
35
|
+
* the previous selection still survives the active filter, otherwise
|
|
36
|
+
* clamped to the last filtered item (or 0 if there are no matches).
|
|
37
|
+
* An open submenu is left untouched — its lifetime is bounded by its own
|
|
38
|
+
* done callback, and `#closeSubmenu` re-clamps the restored index on exit.
|
|
36
39
|
*/
|
|
37
40
|
setItems(items: SettingItem[]): void;
|
|
38
41
|
invalidate(): void;
|
|
39
|
-
render(width: number): string[];
|
|
42
|
+
render(width: number): readonly string[];
|
|
40
43
|
handleInput(data: string): void;
|
|
41
44
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ring buffer for Emacs-style kill/yank operations.
|
|
3
|
-
*
|
|
4
|
-
* Tracks killed (deleted) text entries. Consecutive kills can accumulate
|
|
5
|
-
* into a single entry. Supports yank (paste most recent) and yank-pop
|
|
6
|
-
* (cycle through older entries).
|
|
7
|
-
*/
|
|
8
1
|
export declare class KillRing {
|
|
9
2
|
#private;
|
|
10
3
|
/**
|
|
@@ -23,6 +23,17 @@ export type StdinBufferOptions = {
|
|
|
23
23
|
* After this time, a genuinely incomplete escape is flushed.
|
|
24
24
|
*/
|
|
25
25
|
timeout?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Paste-mode inactivity watchdog (default: 1000ms). If no input arrives for
|
|
28
|
+
* this long while waiting for the bracketed-paste end marker, the paste is
|
|
29
|
+
* assumed truncated: accumulated bytes are delivered and input recovers.
|
|
30
|
+
*/
|
|
31
|
+
pasteTimeout?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Paste-mode byte cap (default: 64 MiB). Exceeding it aborts paste mode the
|
|
34
|
+
* same way, bounding memory when the end marker never arrives.
|
|
35
|
+
*/
|
|
36
|
+
pasteByteLimit?: number;
|
|
26
37
|
};
|
|
27
38
|
export type StdinBufferEventMap = {
|
|
28
39
|
data: [string];
|
|
@@ -16,22 +16,13 @@ export declare class TerminalInfo {
|
|
|
16
16
|
readonly trueColor: boolean;
|
|
17
17
|
readonly hyperlinks: boolean;
|
|
18
18
|
readonly notifyProtocol: NotifyProtocol;
|
|
19
|
-
readonly eagerEraseScrollbackRisk: boolean;
|
|
20
19
|
readonly deccara: boolean;
|
|
21
20
|
readonly supportsScreenToScrollback: boolean;
|
|
22
21
|
/** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
|
|
23
22
|
readonly textSizing: boolean;
|
|
24
|
-
constructor(id: TerminalId, imageProtocol: ImageProtocol | null, trueColor: boolean, hyperlinks: boolean, notifyProtocol?: NotifyProtocol,
|
|
23
|
+
constructor(id: TerminalId, imageProtocol: ImageProtocol | null, trueColor: boolean, hyperlinks: boolean, notifyProtocol?: NotifyProtocol, deccara?: boolean, supportsScreenToScrollback?: boolean,
|
|
25
24
|
/** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
|
|
26
25
|
textSizing?: boolean);
|
|
27
|
-
/**
|
|
28
|
-
* Whether a prompt-submit keystroke scrolls this host to its tail, so the
|
|
29
|
-
* native-scrollback reconciliation checkpoint may ED3-rebuild even when the
|
|
30
|
-
* viewport position is unprobeable. Assigned by the TERMINAL builder from
|
|
31
|
-
* {@link detectSubmitPinsViewportToTail}; readonly but tests opt in via the
|
|
32
|
-
* {@link setTerminalSubmitPinsViewportToTail} mutable-cast setter.
|
|
33
|
-
*/
|
|
34
|
-
readonly submitPinsViewportToTail: boolean;
|
|
35
26
|
/**
|
|
36
27
|
* Mutable clone for the {@link TERMINAL} singleton: copies every field and
|
|
37
28
|
* keeps the prototype methods, so the builder and runtime setters flip
|
|
@@ -50,36 +41,6 @@ export declare function isNotificationSuppressed(): boolean;
|
|
|
50
41
|
* Windows Terminal introduced SIXEL support in preview 1.22.
|
|
51
42
|
*/
|
|
52
43
|
export declare function isWindowsTerminalPreviewSixelSupported(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Whether live-frame native scrollback rebuilds are unsafe when the terminal
|
|
55
|
-
* viewport position is unobservable.
|
|
56
|
-
*
|
|
57
|
-
* A TUI history rebuild emits xterm ED3 (`CSI 3 J`, erase saved lines). Many
|
|
58
|
-
* terminals either clamp a scrolled reader back to the active tail or erase host
|
|
59
|
-
* scrollback when ED3 lands. The important property is not the brand name — it
|
|
60
|
-
* is that an unknown viewport position cannot be proven safe. Environment
|
|
61
|
-
* markers are therefore only used to prove *risk* or a strongly-known profile;
|
|
62
|
-
* unknown POSIX/remote/multiplexer shapes default to risky for passive renders.
|
|
63
|
-
*
|
|
64
|
-
* Native win32 is excluded here because the renderer has dedicated ConPTY
|
|
65
|
-
* deferral paths; a `WT_SESSION` sighting on POSIX means Windows Terminal is the
|
|
66
|
-
* outer host fronting WSL, where the same ED3 yank applies. See #1610/#1682/#1799.
|
|
67
|
-
*/
|
|
68
|
-
export declare function detectTerminalEagerEraseScrollbackRisk(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Whether a prompt-submit keystroke scrolls this terminal to its tail, making the
|
|
71
|
-
* native-scrollback reconciliation checkpoint (`refreshNativeScrollbackIfDirty`)
|
|
72
|
-
* safe to ED3-rebuild even when the viewport position cannot be probed.
|
|
73
|
-
*
|
|
74
|
-
* True only for recognized genuine *local* terminals where typing into the prompt
|
|
75
|
-
* brings the host viewport to the bottom. False — the checkpoint keeps deferring
|
|
76
|
-
* until a positive at-tail probe — for hosts whose scrollback a keystroke does not
|
|
77
|
-
* move: Windows consoles/ConPTY, Windows Terminal (incl. WSL), SSH, multiplexers,
|
|
78
|
-
* and unrecognized profiles. This is the per-terminal counterpart to the blanket
|
|
79
|
-
* block from #1610/#1682/#1746: those hosts genuinely cannot treat a submit as
|
|
80
|
-
* proof of at-tail, but genuine local terminals can.
|
|
81
|
-
*/
|
|
82
|
-
export declare function detectSubmitPinsViewportToTail(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
83
44
|
/**
|
|
84
45
|
* Resolve an explicit user override for DEC 2026 synchronized output. Returns
|
|
85
46
|
* `false` for an opt-out, `true` for a force-on, or `null` when the user has
|
|
@@ -134,11 +95,9 @@ export declare const TERMINAL_ID: TerminalId;
|
|
|
134
95
|
export interface RuntimeTerminal extends TerminalInfo {
|
|
135
96
|
imageProtocol: ImageProtocol | null;
|
|
136
97
|
hyperlinks: boolean;
|
|
137
|
-
eagerEraseScrollbackRisk: boolean;
|
|
138
98
|
deccara: boolean;
|
|
139
99
|
supportsScreenToScrollback: boolean;
|
|
140
100
|
textSizing: boolean;
|
|
141
|
-
submitPinsViewportToTail: boolean;
|
|
142
101
|
}
|
|
143
102
|
export declare const TERMINAL: RuntimeTerminal;
|
|
144
103
|
/**
|
|
@@ -153,8 +112,6 @@ export declare function setTerminalImageProtocol(imageProtocol: ImageProtocol |
|
|
|
153
112
|
export declare function setTerminalDeccara(enabled: boolean): void;
|
|
154
113
|
/** Override screen-to-scrollback clear support for targeted renderer tests. */
|
|
155
114
|
export declare function setTerminalScreenToScrollback(enabled: boolean): void;
|
|
156
|
-
/** Override submit-pins-viewport-to-tail for checkpoint reconciliation tests. */
|
|
157
|
-
export declare function setTerminalSubmitPinsViewportToTail(enabled: boolean): void;
|
|
158
115
|
/**
|
|
159
116
|
* Enable/disable OSC 66 text-sizing at runtime. The coding-agent calls this from
|
|
160
117
|
* the `tui.textSizing` setting (gated on the terminal's static `textSizing`
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -48,42 +48,6 @@ export interface Terminal {
|
|
|
48
48
|
clearScreen(): void;
|
|
49
49
|
setTitle(title: string): void;
|
|
50
50
|
setProgress(active: boolean): void;
|
|
51
|
-
/**
|
|
52
|
-
* Returns whether the native terminal viewport is at the scrollback tail when
|
|
53
|
-
* the host exposes that state. `undefined` means the terminal cannot report it.
|
|
54
|
-
*
|
|
55
|
-
* `ProcessTerminal` deliberately does not implement this — no real terminal
|
|
56
|
-
* can answer it truthfully:
|
|
57
|
-
*
|
|
58
|
-
* - POSIX terminals expose no scrollback-position API at all.
|
|
59
|
-
* - Every modern Windows terminal host (Windows Terminal, VS Code, Tabby,
|
|
60
|
-
* Hyper, Alacritty, WezTerm, JetBrains, …) fronts console apps through
|
|
61
|
-
* ConPTY, where kernel32's `GetConsoleScreenBufferInfo` describes the
|
|
62
|
-
* pseudo-console buffer. That buffer is pinned to the visible grid —
|
|
63
|
-
* scrollback lives in the host UI, invisible to console APIs
|
|
64
|
-
* (microsoft/terminal#10191) — so a probe reads "at bottom" no matter
|
|
65
|
-
* where the user scrolled. Trusting it let streaming-time rebuilds emit
|
|
66
|
-
* `\x1b[3J` and yank scrolled readers: #1635 (Windows Terminal), #1746
|
|
67
|
-
* (Tabby and other ConPTY hosts). No env var distinguishes these hosts
|
|
68
|
-
* (Tabby sets none), so trust cannot be conditional on the environment.
|
|
69
|
-
* - Legacy conhost (the only non-ConPTY host) keeps a real scrollback
|
|
70
|
-
* buffer, but its window follows the output cursor: a probe comparing
|
|
71
|
-
* `srWindow.Bottom` against `dwSize.Y - 1` reads "scrolled up" for a user
|
|
72
|
-
* following live output until all ~9001 buffer rows fill, permanently
|
|
73
|
-
* blocking checkpoint scrollback reconciliation.
|
|
74
|
-
*
|
|
75
|
-
* The renderer treats a missing implementation / `undefined` as "unknown":
|
|
76
|
-
* live mutations defer destructive rebuilds and reconcile native scrollback
|
|
77
|
-
* at explicit checkpoints (prompt submit), where the user's keystroke has
|
|
78
|
-
* already pinned the host viewport to the bottom. Only test terminals
|
|
79
|
-
* (xterm.js-backed) implement this with a real answer.
|
|
80
|
-
*/
|
|
81
|
-
isNativeViewportAtBottom?(): boolean | undefined;
|
|
82
|
-
/**
|
|
83
|
-
* Override the global terminal-profile ED3 risk decision for custom/test
|
|
84
|
-
* terminals. `undefined` falls back to the resolved `TERMINAL` profile.
|
|
85
|
-
*/
|
|
86
|
-
hasEagerEraseScrollbackRisk?(): boolean | undefined;
|
|
87
51
|
/**
|
|
88
52
|
* Register a callback for terminal appearance (dark/light) changes.
|
|
89
53
|
* Detection uses OSC 11 background color query with Mode 2031 as a change trigger.
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -24,14 +24,26 @@ export interface TUIStartOptions {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Component interface - all components must implement this
|
|
27
|
+
*
|
|
28
|
+
* Render contract: the returned array (and its rows) belongs to the component.
|
|
29
|
+
* Callers MUST NOT mutate it — components are allowed to return a cached array
|
|
30
|
+
* and will return the exact same reference for as long as their rendered
|
|
31
|
+
* content is unchanged. Conversely, a component MUST return a fresh array
|
|
32
|
+
* reference whenever its content changed; reference equality across two
|
|
33
|
+
* render() calls is the engine's proof that the rows are byte-identical
|
|
34
|
+
* (containers memoize their concatenation on it, and the TUI derives the
|
|
35
|
+
* frame's stable prefix from it). A component that mutates a previously
|
|
36
|
+
* returned array in place must implement {@link RenderStablePrefix} to declare
|
|
37
|
+
* which leading rows survived.
|
|
27
38
|
*/
|
|
28
39
|
export interface Component {
|
|
29
40
|
/**
|
|
30
|
-
* Render the component to
|
|
31
|
-
*
|
|
32
|
-
*
|
|
41
|
+
* Render the component to an array of physical rows at the given width.
|
|
42
|
+
* The result is component-owned and `readonly` to the caller; an unchanged
|
|
43
|
+
* component may (and should) return the same array reference it returned
|
|
44
|
+
* last time.
|
|
33
45
|
*/
|
|
34
|
-
render(width: number): string[];
|
|
46
|
+
render(width: number): readonly string[];
|
|
35
47
|
/**
|
|
36
48
|
* Optional handler for keyboard input when component has focus
|
|
37
49
|
*/
|
|
@@ -55,27 +67,50 @@ export interface Component {
|
|
|
55
67
|
dispose?(): void;
|
|
56
68
|
}
|
|
57
69
|
/**
|
|
58
|
-
*
|
|
59
|
-
* renders a
|
|
60
|
-
* line index where that suffix begins after each render.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
70
|
+
* Component seam for append-only native-scrollback commits. A component that
|
|
71
|
+
* renders a finalized prefix followed by a live/mutating suffix reports the
|
|
72
|
+
* local line index where that suffix begins after each render. The engine
|
|
73
|
+
* commits rows to native scrollback only up to that boundary; everything
|
|
74
|
+
* below repaints in place inside the visible window and never enters history
|
|
75
|
+
* until it finalizes.
|
|
63
76
|
*
|
|
64
77
|
* `getNativeScrollbackCommitSafeEnd` optionally reports a *deeper* boundary
|
|
65
|
-
* inside
|
|
66
|
-
* append-only (
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* deferred. Defaults to `liveRegionStart` when absent.
|
|
78
|
+
* inside the live suffix: the line index up to which the live region is
|
|
79
|
+
* append-only (earlier rows never re-layout — a streaming assistant message).
|
|
80
|
+
* Rows in `[liveRegionStart, commitSafeEnd)` may commit even though they are
|
|
81
|
+
* technically live, because they will never change. Without it, a single live
|
|
82
|
+
* block that alone overflows the window would hold its scrolled-off head out
|
|
83
|
+
* of history until it finalizes. Volatile live blocks (tool previews that
|
|
84
|
+
* collapse) omit it. Defaults to `liveRegionStart` when absent; a root that
|
|
85
|
+
* reports no seam at all commits everything that scrolls (shell semantics).
|
|
74
86
|
*/
|
|
75
87
|
export interface NativeScrollbackLiveRegion {
|
|
76
88
|
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
77
89
|
getNativeScrollbackCommitSafeEnd?(): number | undefined;
|
|
78
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Opt-in stability report for components that mutate their returned render
|
|
93
|
+
* array in place across frames (instead of returning a fresh array per
|
|
94
|
+
* change). The engine reads it right after the component's `render()` returns:
|
|
95
|
+
* the report counts the leading rows of the just-returned array that are
|
|
96
|
+
* byte-identical to the array state the reader last observed. The engine uses
|
|
97
|
+
* it to reuse the composed frame's prefix — skipping marker extraction, line
|
|
98
|
+
* preparation, and the committed-prefix audit for those rows.
|
|
99
|
+
*
|
|
100
|
+
* Contract:
|
|
101
|
+
* - Reading CONSUMES the report: it re-bases the baseline to the current
|
|
102
|
+
* array state. The accumulated count therefore covers every render since
|
|
103
|
+
* the previous read, so out-of-band `render()` calls between engine frames
|
|
104
|
+
* (an exporter walking the tree) can only lower the report, never inflate
|
|
105
|
+
* it past what the engine actually has.
|
|
106
|
+
* - An implementer that cannot prove stability for a frame must lower the
|
|
107
|
+
* accumulated count to 0 for that render.
|
|
108
|
+
* - Rows at or beyond the report may have been mutated in place; rows before
|
|
109
|
+
* it must be the identical string values at the identical indices.
|
|
110
|
+
*/
|
|
111
|
+
export interface RenderStablePrefix {
|
|
112
|
+
getRenderStablePrefixRows(): number;
|
|
113
|
+
}
|
|
79
114
|
/**
|
|
80
115
|
* Interface for components that can receive focus and display a cursor.
|
|
81
116
|
* When focused, the component should emit CURSOR_MARKER at the cursor position
|
|
@@ -97,21 +132,6 @@ export interface Focusable {
|
|
|
97
132
|
export interface RenderRequestOptions {
|
|
98
133
|
/** Clear terminal scrollback for intentional transcript replacement. */
|
|
99
134
|
clearScrollback?: boolean;
|
|
100
|
-
/**
|
|
101
|
-
* Allow a transient live-viewport repaint when the terminal cannot report
|
|
102
|
-
* whether its native viewport is at the tail.
|
|
103
|
-
*
|
|
104
|
-
* This is **not** a settled transcript commit and must not be used for tool
|
|
105
|
-
* completion, session replay, or other background/offscreen rewrites. On
|
|
106
|
-
* ED3-risk terminals it may deliberately choose a viewport repaint/deferred
|
|
107
|
-
* shrink without clearing native scrollback so autocomplete, IME, and focused
|
|
108
|
-
* editor chrome stay responsive without yanking a scrolled reader.
|
|
109
|
-
*/
|
|
110
|
-
allowUnknownViewportMutation?: boolean;
|
|
111
|
-
}
|
|
112
|
-
/** Options for deferred native scrollback rebuild checkpoints. Reserved for API stability. */
|
|
113
|
-
export interface NativeScrollbackRefreshOptions {
|
|
114
|
-
allowUnknownViewport?: boolean;
|
|
115
135
|
}
|
|
116
136
|
/** Type guard to check if a component implements Focusable */
|
|
117
137
|
export declare function isFocusable(component: Component | null): component is Component & Focusable;
|
|
@@ -192,6 +212,7 @@ export interface OverlayHandle {
|
|
|
192
212
|
* Container - a component that contains other components
|
|
193
213
|
*/
|
|
194
214
|
export declare class Container implements Component {
|
|
215
|
+
#private;
|
|
195
216
|
children: Component[];
|
|
196
217
|
addChild(component: Component): void;
|
|
197
218
|
removeChild(component: Component): void;
|
|
@@ -203,8 +224,28 @@ export declare class Container implements Component {
|
|
|
203
224
|
* {@link clear} for that). Idempotent per child via each child's own dispose.
|
|
204
225
|
*/
|
|
205
226
|
dispose(): void;
|
|
206
|
-
render(width: number): string[];
|
|
227
|
+
render(width: number): readonly string[];
|
|
207
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Decide whether `frame` still aligns with the committed prefix, and where to
|
|
231
|
+
* re-anchor the commit index when it does not. Returns the resync row index,
|
|
232
|
+
* or -1 when no resync is needed.
|
|
233
|
+
*
|
|
234
|
+
* The detector exploits the asymmetry between the two mutation classes: an
|
|
235
|
+
* in-place edit or restyle of committed rows disturbs only the touched rows
|
|
236
|
+
* (alignment below them is intact — the stale copy in history is the
|
|
237
|
+
* long-accepted artifact), while any insertion or deletion shifts EVERY row
|
|
238
|
+
* below it, including the rows just above the commit boundary. So the prefix
|
|
239
|
+
* *tail* is sampled (up to 8 non-blank rows within the last 24, compared
|
|
240
|
+
* SGR-stripped so theme changes stay quiet, tolerating one mismatch for a
|
|
241
|
+
* legitimate single-row edit): aligned ⇒ no resync; misaligned ⇒ resync at
|
|
242
|
+
* the first non-equivalent row, recommitting from there — duplication, never
|
|
243
|
+
* loss. Highly repetitive tails (identical filler rows) can mask a shift, in
|
|
244
|
+
* which case the skipped rows are content-identical to the committed ones —
|
|
245
|
+
* observationally harmless. Exported for the render-stress harness, whose
|
|
246
|
+
* shadow commit ledger must mirror the engine's law exactly.
|
|
247
|
+
*/
|
|
248
|
+
export declare function findCommittedPrefixResync(frame: readonly string[], prefix: readonly string[]): number;
|
|
208
249
|
/**
|
|
209
250
|
* TUI - Main class for managing terminal UI with differential rendering
|
|
210
251
|
*/
|
|
@@ -220,7 +261,7 @@ export declare class TUI extends Container {
|
|
|
220
261
|
hidden: boolean;
|
|
221
262
|
}[];
|
|
222
263
|
constructor(terminal: Terminal, showHardwareCursor?: boolean, options?: TUIOptions);
|
|
223
|
-
render(width: number): string[];
|
|
264
|
+
render(width: number): readonly string[];
|
|
224
265
|
get fullRedraws(): number;
|
|
225
266
|
/** Shared budget that caps how many inline images render as live graphics. */
|
|
226
267
|
get imageBudget(): ImageBudget;
|
|
@@ -232,13 +273,6 @@ export declare class TUI extends Container {
|
|
|
232
273
|
setMaxInlineImages(cap: number): void;
|
|
233
274
|
getShowHardwareCursor(): boolean;
|
|
234
275
|
setShowHardwareCursor(enabled: boolean): void;
|
|
235
|
-
getClearOnShrink(): boolean;
|
|
236
|
-
/**
|
|
237
|
-
* Set whether to trigger full re-render when content shrinks.
|
|
238
|
-
* When true (default), empty rows are cleared when content shrinks.
|
|
239
|
-
* When false, empty rows remain (reduces redraws on slower terminals).
|
|
240
|
-
*/
|
|
241
|
-
setClearOnShrink(enabled: boolean): void;
|
|
242
276
|
/**
|
|
243
277
|
* Whether DEC 2026 synchronized-output wrappers are currently emitted around
|
|
244
278
|
* paints. Starts from conservative terminal/env detection and is reconciled at
|
|
@@ -246,30 +280,6 @@ export declare class TUI extends Container {
|
|
|
246
280
|
* positive report, disabled on a negative one.
|
|
247
281
|
*/
|
|
248
282
|
get synchronizedOutput(): boolean;
|
|
249
|
-
/**
|
|
250
|
-
* When enabled, live render frames rebuild native scrollback on offscreen and
|
|
251
|
-
* structural changes even when the viewport position is unobservable (POSIX,
|
|
252
|
-
* where `isNativeViewportAtBottom()` is `undefined`), instead of deferring to a
|
|
253
|
-
* non-destructive repaint. This trades the anti-yank guarantee for a clean,
|
|
254
|
-
* duplicate-free history and is meant for windows where output above the fold
|
|
255
|
-
* is actively re-rendering — e.g. a tool whose result is still streaming and
|
|
256
|
-
* re-laying-out rows that have already scrolled into history. A terminal that
|
|
257
|
-
* reports a *known*-scrolled viewport still defers, as does native Windows
|
|
258
|
-
* (the viewport is never observable there and ConPTY hosts erase host
|
|
259
|
-
* scrollback on ED3 — #1635/#1746); only the unknown POSIX case is forced to
|
|
260
|
-
* rebuild. POSIX hosts known to disturb scrolled readers on xterm ED3
|
|
261
|
-
* (`CSI 3 J`, erase saved lines) also defer the eager opt-in; checkpoint
|
|
262
|
-
* rebuilds are unaffected.
|
|
263
|
-
*
|
|
264
|
-
* Disabling stays active through one already-requested frame: the event batch
|
|
265
|
-
* that ends a foreground stream both removes its UI rows (loader/status
|
|
266
|
-
* teardown — a shrink) and clears this flag before the throttled render timer
|
|
267
|
-
* fires. If the flag dropped immediately, that teardown frame would hit the
|
|
268
|
-
* ED3-risk idle deferral and freeze on screen (stale spinner) until the next
|
|
269
|
-
* keystroke. When no render is pending, disable immediately so a later
|
|
270
|
-
* unrelated content mutation does not inherit foreground-stream privileges.
|
|
271
|
-
*/
|
|
272
|
-
setEagerNativeScrollbackRebuild(enabled: boolean): void;
|
|
273
283
|
setFocus(component: Component | null): void;
|
|
274
284
|
/** Component currently receiving keyboard input, if any. */
|
|
275
285
|
getFocused(): Component | null;
|
|
@@ -288,12 +298,6 @@ export declare class TUI extends Container {
|
|
|
288
298
|
addInputListener(listener: InputListener): () => void;
|
|
289
299
|
removeInputListener(listener: InputListener): void;
|
|
290
300
|
stop(): void;
|
|
291
|
-
/**
|
|
292
|
-
* Rebuild native terminal scrollback if live rendering deferred a history rewrite.
|
|
293
|
-
* Callers should only invoke this at checkpoints where the user is expected to be
|
|
294
|
-
* at the terminal bottom, such as after submitting a new prompt.
|
|
295
|
-
*/
|
|
296
|
-
refreshNativeScrollbackIfDirty(_options?: NativeScrollbackRefreshOptions): boolean;
|
|
297
301
|
/**
|
|
298
302
|
* Force an immediate full replay of the current frame, including native
|
|
299
303
|
* scrollback. This is the keyboard-accessible equivalent of the resize reset:
|