@oh-my-pi/pi-tui 15.10.8 → 15.10.10
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 +31 -1
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/select-list.d.ts +8 -0
- package/dist/types/terminal-capabilities.d.ts +1 -44
- package/dist/types/terminal.d.ts +0 -36
- package/dist/types/tui.d.ts +34 -66
- package/package.json +3 -3
- package/src/components/editor.ts +1 -0
- package/src/components/image.ts +37 -5
- package/src/components/select-list.ts +181 -34
- package/src/terminal-capabilities.ts +5 -150
- package/src/terminal.ts +0 -38
- package/src/tui.ts +577 -1791
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.10.10] - 2026-06-09
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
- 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).
|
|
16
|
+
- 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.
|
|
17
|
+
- 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.
|
|
18
|
+
- 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).
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- 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.
|
|
23
|
+
- 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).
|
|
24
|
+
|
|
25
|
+
## [15.10.9] - 2026-06-09
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added a `wrapDescription` option to `SelectListLayoutOptions`. When enabled, long descriptions wrap onto continuation rows indented under the description column instead of being silently truncated. The slash-command/skill autocomplete picker now opts in so descriptions like the bundled skills' remain fully readable at normal terminal widths. `maxVisible` becomes the picker's visual row budget so the popup height stays bounded even when items wrap (a single 5-row description with `maxVisible=3` clips with the scrollbar carrying the offscreen tail). Navigation stays item-to-item, the narrow-width fallback (`width <= 40`) is unchanged, and the `ScrollView` scrollbar tracks visual rows so the thumb stays correct when items wrap unevenly. ([#2169](https://github.com/can1357/oh-my-pi/issues/2169))
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Fixed Ghostty's first inline image in a fresh TUI session sometimes rendering as an empty placeholder block by holding the initial Kitty graphics paint until the terminal startup settle window has passed. Direct Kitty placements also keep their zero-width reservation rows non-plain so image-only transcript blocks do not collapse when blank-edge trimming runs.
|
|
34
|
+
|
|
5
35
|
## [15.10.8] - 2026-06-09
|
|
6
36
|
|
|
7
37
|
### Fixed
|
|
@@ -1218,4 +1248,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
|
|
1218
1248
|
|
|
1219
1249
|
### Fixed
|
|
1220
1250
|
|
|
1221
|
-
- **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
|
|
1251
|
+
- **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
|
|
@@ -73,6 +73,8 @@ export declare class ImageBudget {
|
|
|
73
73
|
* repeated call (e.g. a width-change re-render) never re-sends the data.
|
|
74
74
|
*/
|
|
75
75
|
enqueueTransmit(imageId: number, sequence: string): void;
|
|
76
|
+
/** Whether a frame has image data queued but not yet written to the terminal. */
|
|
77
|
+
hasPendingTransmits(): boolean;
|
|
76
78
|
/** Transmit sequences to write before this frame's placements; clears the queue. */
|
|
77
79
|
takeTransmits(): readonly string[];
|
|
78
80
|
}
|
|
@@ -28,6 +28,14 @@ export interface SelectListLayoutOptions {
|
|
|
28
28
|
truncatePrimary?: (context: SelectListTruncatePrimaryContext) => string;
|
|
29
29
|
/** Enable type-to-filter search when the item count exceeds maxVisible. Defaults to true. */
|
|
30
30
|
overflowSearch?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Wrap long descriptions onto continuation rows indented under the
|
|
33
|
+
* description column instead of truncating. Defaults to false so existing
|
|
34
|
+
* single-line consumers are unaffected. Navigation remains item-to-item;
|
|
35
|
+
* the scrollbar tracks visual rows so the thumb stays correct when items
|
|
36
|
+
* wrap unevenly.
|
|
37
|
+
*/
|
|
38
|
+
wrapDescription?: boolean;
|
|
31
39
|
}
|
|
32
40
|
export declare class SelectList implements Component {
|
|
33
41
|
#private;
|
|
@@ -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
|
@@ -55,22 +55,22 @@ export interface Component {
|
|
|
55
55
|
dispose?(): void;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
* renders a
|
|
60
|
-
* line index where that suffix begins after each render.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
58
|
+
* Component seam for append-only native-scrollback commits. A component that
|
|
59
|
+
* renders a finalized prefix followed by a live/mutating suffix reports the
|
|
60
|
+
* local line index where that suffix begins after each render. The engine
|
|
61
|
+
* commits rows to native scrollback only up to that boundary; everything
|
|
62
|
+
* below repaints in place inside the visible window and never enters history
|
|
63
|
+
* until it finalizes.
|
|
63
64
|
*
|
|
64
65
|
* `getNativeScrollbackCommitSafeEnd` optionally reports a *deeper* boundary
|
|
65
|
-
* inside
|
|
66
|
-
* append-only (
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* deferred. Defaults to `liveRegionStart` when absent.
|
|
66
|
+
* inside the live suffix: the line index up to which the live region is
|
|
67
|
+
* append-only (earlier rows never re-layout — a streaming assistant message).
|
|
68
|
+
* Rows in `[liveRegionStart, commitSafeEnd)` may commit even though they are
|
|
69
|
+
* technically live, because they will never change. Without it, a single live
|
|
70
|
+
* block that alone overflows the window would hold its scrolled-off head out
|
|
71
|
+
* of history until it finalizes. Volatile live blocks (tool previews that
|
|
72
|
+
* collapse) omit it. Defaults to `liveRegionStart` when absent; a root that
|
|
73
|
+
* reports no seam at all commits everything that scrolls (shell semantics).
|
|
74
74
|
*/
|
|
75
75
|
export interface NativeScrollbackLiveRegion {
|
|
76
76
|
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
@@ -97,21 +97,6 @@ export interface Focusable {
|
|
|
97
97
|
export interface RenderRequestOptions {
|
|
98
98
|
/** Clear terminal scrollback for intentional transcript replacement. */
|
|
99
99
|
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
100
|
}
|
|
116
101
|
/** Type guard to check if a component implements Focusable */
|
|
117
102
|
export declare function isFocusable(component: Component | null): component is Component & Focusable;
|
|
@@ -205,6 +190,26 @@ export declare class Container implements Component {
|
|
|
205
190
|
dispose(): void;
|
|
206
191
|
render(width: number): string[];
|
|
207
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Decide whether `frame` still aligns with the committed prefix, and where to
|
|
195
|
+
* re-anchor the commit index when it does not. Returns the resync row index,
|
|
196
|
+
* or -1 when no resync is needed.
|
|
197
|
+
*
|
|
198
|
+
* The detector exploits the asymmetry between the two mutation classes: an
|
|
199
|
+
* in-place edit or restyle of committed rows disturbs only the touched rows
|
|
200
|
+
* (alignment below them is intact — the stale copy in history is the
|
|
201
|
+
* long-accepted artifact), while any insertion or deletion shifts EVERY row
|
|
202
|
+
* below it, including the rows just above the commit boundary. So the prefix
|
|
203
|
+
* *tail* is sampled (up to 8 non-blank rows within the last 24, compared
|
|
204
|
+
* SGR-stripped so theme changes stay quiet, tolerating one mismatch for a
|
|
205
|
+
* legitimate single-row edit): aligned ⇒ no resync; misaligned ⇒ resync at
|
|
206
|
+
* the first non-equivalent row, recommitting from there — duplication, never
|
|
207
|
+
* loss. Highly repetitive tails (identical filler rows) can mask a shift, in
|
|
208
|
+
* which case the skipped rows are content-identical to the committed ones —
|
|
209
|
+
* observationally harmless. Exported for the render-stress harness, whose
|
|
210
|
+
* shadow commit ledger must mirror the engine's law exactly.
|
|
211
|
+
*/
|
|
212
|
+
export declare function findCommittedPrefixResync(frame: readonly string[], prefix: readonly string[]): number;
|
|
208
213
|
/**
|
|
209
214
|
* TUI - Main class for managing terminal UI with differential rendering
|
|
210
215
|
*/
|
|
@@ -232,13 +237,6 @@ export declare class TUI extends Container {
|
|
|
232
237
|
setMaxInlineImages(cap: number): void;
|
|
233
238
|
getShowHardwareCursor(): boolean;
|
|
234
239
|
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
240
|
/**
|
|
243
241
|
* Whether DEC 2026 synchronized-output wrappers are currently emitted around
|
|
244
242
|
* paints. Starts from conservative terminal/env detection and is reconciled at
|
|
@@ -246,30 +244,6 @@ export declare class TUI extends Container {
|
|
|
246
244
|
* positive report, disabled on a negative one.
|
|
247
245
|
*/
|
|
248
246
|
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
247
|
setFocus(component: Component | null): void;
|
|
274
248
|
/** Component currently receiving keyboard input, if any. */
|
|
275
249
|
getFocused(): Component | null;
|
|
@@ -288,12 +262,6 @@ export declare class TUI extends Container {
|
|
|
288
262
|
addInputListener(listener: InputListener): () => void;
|
|
289
263
|
removeInputListener(listener: InputListener): void;
|
|
290
264
|
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
265
|
/**
|
|
298
266
|
* Force an immediate full replay of the current frame, including native
|
|
299
267
|
* scrollback. This is the keyboard-accessible equivalent of the resize reset:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "15.10.
|
|
4
|
+
"version": "15.10.10",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "15.10.
|
|
41
|
-
"@oh-my-pi/pi-utils": "15.10.
|
|
40
|
+
"@oh-my-pi/pi-natives": "15.10.10",
|
|
41
|
+
"@oh-my-pi/pi-utils": "15.10.10",
|
|
42
42
|
"lru-cache": "11.5.1",
|
|
43
43
|
"marked": "^18.0.4"
|
|
44
44
|
},
|
package/src/components/editor.ts
CHANGED
package/src/components/image.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface ImageOptions {
|
|
|
27
27
|
|
|
28
28
|
const EMPTY_IDS: readonly number[] = [];
|
|
29
29
|
const EMPTY_TRANSMITS: readonly string[] = [];
|
|
30
|
+
// Direct placements reserve height with leading zero-width rows. Keep them
|
|
31
|
+
// non-plain so transcript blank-edge trimming does not collapse image-only blocks.
|
|
32
|
+
const RESERVED_IMAGE_ROW = "\x1b[0m";
|
|
30
33
|
|
|
31
34
|
/** Default count of inline images kept as live graphics before older ones fall back to text. */
|
|
32
35
|
export const DEFAULT_MAX_INLINE_IMAGES = 8;
|
|
@@ -188,6 +191,11 @@ export class ImageBudget {
|
|
|
188
191
|
this.#pendingTransmits.push(sequence);
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
/** Whether a frame has image data queued but not yet written to the terminal. */
|
|
195
|
+
hasPendingTransmits(): boolean {
|
|
196
|
+
return this.#pendingTransmits.length > 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
191
199
|
/** Transmit sequences to write before this frame's placements; clears the queue. */
|
|
192
200
|
takeTransmits(): readonly string[] {
|
|
193
201
|
if (this.#pendingTransmits.length === 0) return EMPTY_TRANSMITS;
|
|
@@ -232,6 +240,10 @@ export class Image implements Component {
|
|
|
232
240
|
#cachedLines?: string[];
|
|
233
241
|
#cachedWidth?: number;
|
|
234
242
|
#cachedSuppressed = false;
|
|
243
|
+
// Tallest graphic placement this image has rendered. The text fallback
|
|
244
|
+
// pads itself to this height so a budget demotion never shrinks the block
|
|
245
|
+
// (its rows may already be committed to native scrollback).
|
|
246
|
+
#renderedGraphicRows = 0;
|
|
235
247
|
|
|
236
248
|
constructor(
|
|
237
249
|
base64Data: string,
|
|
@@ -296,17 +308,16 @@ export class Image implements Component {
|
|
|
296
308
|
// moves the cursor back up, then emits the image sequence.
|
|
297
309
|
lines = [];
|
|
298
310
|
for (let i = 0; i < result.rows - 1; i++) {
|
|
299
|
-
lines.push(
|
|
311
|
+
lines.push(RESERVED_IMAGE_ROW);
|
|
300
312
|
}
|
|
301
313
|
const moveUp = result.rows > 1 ? `\x1b[${result.rows - 1}A` : "";
|
|
302
314
|
lines.push(moveUp + (result.sequence ?? ""));
|
|
303
315
|
} else {
|
|
304
|
-
lines =
|
|
305
|
-
this.#theme.fallbackColor(imageFallback(this.#mimeType, this.#dimensions, this.#options.filename)),
|
|
306
|
-
];
|
|
316
|
+
lines = this.#fallbackLines();
|
|
307
317
|
}
|
|
318
|
+
this.#renderedGraphicRows = Math.max(this.#renderedGraphicRows, lines.length);
|
|
308
319
|
} else {
|
|
309
|
-
lines =
|
|
320
|
+
lines = this.#fallbackLines();
|
|
310
321
|
}
|
|
311
322
|
|
|
312
323
|
this.#cachedLines = lines;
|
|
@@ -315,4 +326,25 @@ export class Image implements Component {
|
|
|
315
326
|
|
|
316
327
|
return lines;
|
|
317
328
|
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Text fallback, height-preserving once a graphic has rendered: a demoted
|
|
332
|
+
* image must keep occupying the rows its placement used, because those
|
|
333
|
+
* rows may already be committed to native scrollback — shrinking the block
|
|
334
|
+
* would shift everything below it and force the renderer's commit-resync
|
|
335
|
+
* (stale band + recommit). Reserved rows stay non-plain so blank-edge
|
|
336
|
+
* trimming cannot collapse the block either.
|
|
337
|
+
*/
|
|
338
|
+
#fallbackLines(): string[] {
|
|
339
|
+
const fallback = this.#theme.fallbackColor(
|
|
340
|
+
imageFallback(this.#mimeType, this.#dimensions, this.#options.filename),
|
|
341
|
+
);
|
|
342
|
+
if (this.#renderedGraphicRows <= 1) return [fallback];
|
|
343
|
+
const lines: string[] = [];
|
|
344
|
+
for (let i = 0; i < this.#renderedGraphicRows - 1; i++) {
|
|
345
|
+
lines.push(RESERVED_IMAGE_ROW);
|
|
346
|
+
}
|
|
347
|
+
lines.push(fallback);
|
|
348
|
+
return lines;
|
|
349
|
+
}
|
|
318
350
|
}
|