@oh-my-pi/pi-tui 18.0.0 → 18.0.3

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 CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.0.3] - 2026-08-23
6
+
7
+ ### Fixed
8
+
9
+ - Fixed inline images vanishing from the transcript and scrollback when the session exits: stop no longer deletes transmitted Kitty images from the terminal's graphics store.
10
+
11
+ ## [18.0.2] - 2026-08-23
12
+
13
+ ### Fixed
14
+
15
+ - Fixed visible history being erased when enlarging the terminal.
16
+
17
+ ## [18.0.1] - 2026-08-23
18
+
19
+ ### Added
20
+
21
+ - Collapsed individual skill commands into a `/skill:` namespace entry to declutter suggestions
22
+ - Added `TUI.renderNow()` for terminal-safe synchronous priority frames that retain resize debounce, output-backlog, and image deferral safeguards.
23
+
24
+ ### Changed
25
+
26
+ - Improved slash command autocompletion to chain suggestions after selecting a namespace
27
+ - Replaced the native-scrollback inference API (`NativeScrollback*` interfaces and the scrollback rebuild/resize settings hooks) with explicit `TerminalFramePlan` history batches.
28
+ - Post-resize repaints now recover the reflowed viewport anchor with a cursor-position report (DSR) instead of trusting stale grid coordinates, so a settled resize no longer duplicates the editor/status rows on screen.
29
+ - History appends that overflow the screen erase the old live viewport first, so a scroll can only push committed rows and blanks into scrollback, never an unfinished frame.
30
+
31
+ ### Fixed
32
+
33
+ - Fixed consecutive prompt submissions being skipped by persistent history, allowing the latest project metadata to replace the previous entry without duplicating editor navigation history.
34
+ - Fixed the history drain stalling on idle screens: accepting a batch now pumps the next frame, so a large resumed transcript retires to terminal history instead of pinning the live viewport in its emergency aggregate.
35
+ - Fixed fuzzy matching so a qualifying whole-word hit is not hidden by an earlier mid-word occurrence ([#8465](https://github.com/can1357/oh-my-pi/pull/8465) by [@Mustaqeem66](https://github.com/Mustaqeem66)).
36
+ - Fixed stray characters appearing in the terminal viewport during title updates
37
+ - Fixed editor input lag when autocomplete providers are slow by keeping only the latest pending lookup.
38
+ - Fixed pasting an image in kitty occasionally spraying base64 text into the composer alongside the image attachment: a kitty OSC 5522 clipboard packet torn by the incomplete-escape flush is now discarded up to its terminator instead of being replayed as keystrokes.
39
+ - Fixed Kitty OSC 66 headings activating before the host explicitly enables text sizing.
40
+ - Markdown streaming renderer now scans only the mutable tail (not the full document) for reference-link definitions and CR on every frame, eliminating the O(n²) `RegExp.test` cost that accounted for ~26% CPU during active streaming ([#9048](https://github.com/can1357/oh-my-pi/issues/9048)).
41
+
5
42
  ## [18.0.0] - 2026-08-22
6
43
 
7
44
  ### Breaking Changes
@@ -32,8 +32,8 @@ export interface SlashCommand {
32
32
  getInlineHint?(argumentText: string): string | null;
33
33
  }
34
34
  export interface AutocompleteProvider {
35
- /** Get autocomplete suggestions for current text/cursor position */
36
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
35
+ /** Get autocomplete suggestions for current text/cursor position. Expensive providers SHOULD stop when `signal` aborts. */
36
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
37
37
  items: AutocompleteItem[];
38
38
  prefix: string;
39
39
  } | null>;
@@ -68,8 +68,9 @@ export interface AutocompleteProvider {
68
68
  * Force file-path completion (called on Tab). Returns matched items plus the
69
69
  * full prefix, or null when no path token sits before the cursor. Present on
70
70
  * file-aware providers; absent on slash-only ones.
71
+ * Expensive providers SHOULD stop when `signal` aborts.
71
72
  */
72
- getForceFileSuggestions?(lines: string[], cursorLine: number, cursorCol: number): Promise<{
73
+ getForceFileSuggestions?(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
73
74
  items: AutocompleteItem[];
74
75
  prefix: string;
75
76
  } | null>;
@@ -83,6 +84,7 @@ export interface CombinedAutocompleteOptions {
83
84
  commandUsage?: (name: string) => number;
84
85
  }
85
86
  export declare function scoreCommandTextMatch(lowerPrefix: string, lowerTarget: string): number;
87
+ export declare const SKILL_NAMESPACE = "skill:";
86
88
  /**
87
89
  * Whether a mid-prompt slash token (`prose … /tok`) is skill-shaped enough to
88
90
  * surface `name` in the skill popup. Deliberately stricter than submitted
@@ -100,7 +102,7 @@ export declare function midPromptSkillTokenMatches(lowerToken: string, name: str
100
102
  export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
101
103
  #private;
102
104
  constructor(commands?: CommandEntry[], basePath?: string, options?: CombinedAutocompleteOptions);
103
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
105
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
104
106
  items: AutocompleteItem[];
105
107
  prefix: string;
106
108
  } | null>;
@@ -110,7 +112,7 @@ export declare class CombinedAutocompleteProvider implements AutocompleteProvide
110
112
  cursorCol: number;
111
113
  };
112
114
  invalidateDirCache(dir?: string): void;
113
- getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
115
+ getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
114
116
  items: AutocompleteItem[];
115
117
  prefix: string;
116
118
  } | null>;
@@ -114,9 +114,7 @@ export declare class Editor implements Component, Focusable {
114
114
  * Use this when the top border derives from state that mutates far faster
115
115
  * than the render cadence (session events, streaming, subagent updates).
116
116
  * The TUI already throttles renders, so a provider is invoked exactly once
117
- * per frame and does no work between paints. Return a logical `revision` to
118
- * distinguish concurrent status mutations from pure width reflow.
119
- */
117
+ * per frame and does no work between paints. */
120
118
  setTopBorderProvider(provider: ((availableWidth: number) => EditorTopBorder | undefined) | undefined): void;
121
119
  /**
122
120
  * Show or hide the editor border chrome.
@@ -145,6 +143,7 @@ export declare class Editor implements Component, Focusable {
145
143
  setPaddingX(paddingX: number): void;
146
144
  getAutocompleteMaxVisible(): number;
147
145
  setAutocompleteMaxVisible(maxVisible: number): void;
146
+ /** Loads persistent prompts for navigation and enables future persistence. */
148
147
  setHistoryStorage(storage: HistoryStorage): void;
149
148
  /**
150
149
  * Add a prompt to history for up/down arrow navigation.
@@ -155,7 +154,6 @@ export declare class Editor implements Component, Focusable {
155
154
  render(width: number): readonly string[];
156
155
  handleInput(data: string): void;
157
156
  getText(): string;
158
- getNativeScrollbackWidthEpochRevision(): number;
159
157
  /** Whether the buffer text equals `value`, without `getText()`'s full join —
160
158
  * O(1) for the hot per-keystroke probes against short single-line values. */
161
159
  textEquals(value: string): boolean;
@@ -121,11 +121,8 @@ export declare class ImageBudget {
121
121
  * Restart every placement epoch after a destructive history clear (`CSI 3 J`
122
122
  * full paint). The clear destroys all placement cells — scrollback rows are
123
123
  * gone and the replay rewrites the viewport — so no archive remains to
124
- * protect. Reverting to epoch 1 lets the replay's placements replace the
125
- * terminal's stale registry entries; the returned list names every image
126
- * and the highest epoch it reached so the caller can delete all of its
127
- * registry entries explicitly (`d=i` keeps the transmitted data) — an image
128
- * absent from the replay never re-places, so even its epoch-1 entry must go.
124
+ * protect. Reverting to epoch 1 lets the replay recreate every visible
125
+ * placement after the terminal-wide cleanup.
129
126
  */
130
127
  resetPlacementEpochs(): ReadonlyArray<{
131
128
  imageId: number;
@@ -1,5 +1,5 @@
1
1
  import type { SymbolTheme } from "../symbols.js";
2
- import type { Component, NativeScrollbackCommittedRows, NativeScrollbackReplay, NativeScrollbackWidthEpoch } from "../tui.js";
2
+ import type { Component } from "../tui.js";
3
3
  /** @internal exported for tests — must stay index-identical to the old regex scan. */
4
4
  export declare function mathStartIndex(src: string): number | undefined;
5
5
  /** @internal exported for tests — must stay index-identical to the old regex scan. */
@@ -69,37 +69,14 @@ export interface MarkdownTheme {
69
69
  resolveMermaidAscii?: (source: string, maxWidth?: number) => string | null;
70
70
  symbols: SymbolTheme;
71
71
  }
72
- export declare class Markdown implements Component, NativeScrollbackCommittedRows, NativeScrollbackReplay, NativeScrollbackWidthEpoch {
72
+ export declare class Markdown implements Component {
73
73
  #private;
74
74
  setIgnoreTight(ignore: boolean): this;
75
75
  constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
76
76
  setText(text: string): boolean;
77
- getNativeScrollbackWidthEpochRevision(): number;
78
77
  invalidate(): void;
79
78
  get transientRenderCache(): boolean;
80
79
  set transientRenderCache(value: boolean);
81
- /**
82
- * Rows at the top of the most recent render() (top padding + rendered
83
- * frozen-token prefix) whose bytes are settled: byte-stable at this
84
- * width/theme for as long as the text keeps growing append-only. Hosts
85
- * feed this to transcript commit gating (see the coding agent's
86
- * `FinalizableBlock.getTranscriptBlockSettledRows`). 0 outside streaming
87
- * (`transientRenderCache`) mode, after a text rewind (re-earned on the new
88
- * lineage), and on cache-served non-streaming renders.
89
- */
90
- getLastRenderSettledRows(): number;
91
- captureNativeScrollbackWidthEpoch(): unknown;
92
- resolveNativeScrollbackWidthEpoch(boundary: unknown): number | undefined;
93
- getNativeScrollbackWidthEpochRows(): number | undefined;
94
- isNativeScrollbackWidthEpochAppendOnly(boundary: unknown): boolean;
95
- /**
96
- * Freeze every table whose first physical row is already part of the native
97
- * scrollback prefix. The recorded widths came from the exact frame that was
98
- * just emitted, so the next streamed delta cannot retroactively widen it.
99
- */
100
- setNativeScrollbackCommittedRows(rows: number): void;
101
- /** A destructive replay removes the immutable tape this layout was guarding. */
102
- prepareNativeScrollbackReplay(): void;
103
80
  render(width: number): readonly string[];
104
81
  }
105
82
  /**
@@ -14,7 +14,6 @@ export declare class Text implements Component {
14
14
  constructor(text?: string, paddingX?: number, paddingY?: number, customBgFn?: (text: string) => string);
15
15
  getText(): string;
16
16
  setText(text: string): boolean;
17
- getNativeScrollbackWidthEpochRevision(): number;
18
17
  setCustomBgFn(customBgFn?: (text: string) => string): void;
19
18
  /**
20
19
  * Supply a foreground styler applied to the text at render time (e.g. a
@@ -21,7 +21,7 @@ export declare class TerminalInfo {
21
21
  readonly deccara: boolean;
22
22
  readonly supportsScreenToScrollback: boolean;
23
23
  /** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
24
- readonly textSizing: boolean;
24
+ readonly supportsTextSizing: boolean;
25
25
  /**
26
26
  * Hangul Compatibility Jamo (U+3131..=U+318E) cell width. Ghostty follows
27
27
  * UAX#11 (2 cells); Warp paints 1; "platform" keeps the OS default
@@ -30,7 +30,7 @@ export declare class TerminalInfo {
30
30
  readonly hangulJamoWidth: HangulCompatibilityJamoWidth;
31
31
  constructor(id: TerminalId, imageProtocol: ImageProtocol | null, trueColor: boolean, hyperlinks: boolean, notifyProtocol?: NotifyProtocol, deccara?: boolean, supportsScreenToScrollback?: boolean,
32
32
  /** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
33
- textSizing?: boolean,
33
+ supportsTextSizing?: boolean,
34
34
  /**
35
35
  * Hangul Compatibility Jamo (U+3131..=U+318E) cell width. Ghostty follows
36
36
  * UAX#11 (2 cells); Warp paints 1; "platform" keeps the OS default
@@ -167,6 +167,7 @@ export interface RuntimeTerminal extends TerminalInfo {
167
167
  hyperlinks: boolean;
168
168
  deccara: boolean;
169
169
  supportsScreenToScrollback: boolean;
170
+ /** Whether OSC 66 text sizing is currently enabled. */
170
171
  textSizing: boolean;
171
172
  }
172
173
  export declare const TERMINAL: RuntimeTerminal;
@@ -184,7 +185,7 @@ export declare function setTerminalDeccara(enabled: boolean): void;
184
185
  export declare function setTerminalScreenToScrollback(enabled: boolean): void;
185
186
  /**
186
187
  * Enable/disable OSC 66 text-sizing at runtime. The coding-agent calls this from
187
- * the `tui.textSizing` setting (gated on the terminal's static `textSizing`
188
+ * the `tui.textSizing` setting (gated on the terminal's static `supportsTextSizing`
188
189
  * capability); tests flip it directly to exercise the scaled-heading path.
189
190
  */
190
191
  export declare function setTerminalTextSizing(enabled: boolean): void;
@@ -282,6 +283,12 @@ export declare function encodeKittyPlacementLine(options: {
282
283
  * this is the only way to actually purge a placed image.
283
284
  */
284
285
  export declare function encodeKittyDeleteImage(imageId: number): string;
286
+ /**
287
+ * Delete every Kitty image and placement in the terminal. Used only by an
288
+ * explicit destructive display reset: text erases leave untracked placements
289
+ * painted, so per-image bookkeeping cannot guarantee a clean viewport.
290
+ */
291
+ export declare function encodeKittyDeleteAllImages(): string;
285
292
  /**
286
293
  * Delete a single placement of an image (`d=i`, lowercase): removes its cells
287
294
  * and registry entry but keeps the transmitted data, so a later `a=p` under a
@@ -51,6 +51,16 @@ export declare class OutputBacklogGuard {
51
51
  }
52
52
  /** Record alternate-screen state (called by the TUI on `?1049h`/`?1049l` writes). */
53
53
  export declare function setAltScreenActive(active: boolean): void;
54
+ /**
55
+ * Route an out-of-band escape sequence (e.g. an OSC title update) through the
56
+ * active terminal's output path. While a TUI owns stdout, frame paints go
57
+ * through the off-thread write pump and can split across multiple write(2)
58
+ * calls; a direct main-thread `process.stdout.write` can land between two of
59
+ * them — mid escape sequence — and the host terminal then prints the payload
60
+ * as literal text at the cursor position. Returns false when no terminal has
61
+ * started, in which case the caller owns stdout and may write directly.
62
+ */
63
+ export declare function writeThroughActiveTerminal(data: string): boolean;
54
64
  /**
55
65
  * Emergency terminal restore - call this from signal/crash handlers
56
66
  * Resets terminal state without requiring access to the ProcessTerminal instance
@@ -18,6 +18,30 @@ export interface RenderScheduler {
18
18
  export interface TUIOptions {
19
19
  renderScheduler?: RenderScheduler;
20
20
  }
21
+ /** Physical terminal dimensions supplied to a frame provider. */
22
+ export interface ViewportSize {
23
+ readonly columns: number;
24
+ readonly rows: number;
25
+ }
26
+ /** Immutable finalized rows offered until the terminal accepts this identifier. */
27
+ export interface HistoryBatch {
28
+ readonly id: number;
29
+ readonly rows: readonly string[];
30
+ }
31
+ /** One history append and the complete mutable viewport for a terminal frame. */
32
+ export interface TerminalFramePlan {
33
+ readonly history?: HistoryBatch;
34
+ readonly viewport: readonly string[];
35
+ }
36
+ /** Produces bounded terminal frames and retires acknowledged history batches. */
37
+ export interface TerminalFrameProvider {
38
+ renderFrame(viewport: ViewportSize): TerminalFramePlan;
39
+ acknowledgeHistory(id: number): void;
40
+ /** Full semantic viewport used only on the transient resize buffer. */
41
+ renderResizeFrame?(viewport: ViewportSize): readonly string[];
42
+ /** Re-offer finalized history after a display reset or resize replay. */
43
+ resetHistory?(): void;
44
+ }
21
45
  export interface TUIStartOptions {
22
46
  /** Clear saved native scrollback before the first paint. */
23
47
  clearScrollback?: boolean;
@@ -81,109 +105,6 @@ export interface OverlayFocusOwner {
81
105
  /** Returns true when `component` is a focus target inside this overlay. */
82
106
  ownsOverlayFocusTarget(component: Component): boolean;
83
107
  }
84
- /**
85
- * Component seam for append-only native-scrollback commits. A component whose
86
- * rendered rows can still change reports, after each render, the local line
87
- * index where that mutable suffix begins. Rows above the boundary are declared
88
- * FINAL — byte-stable at the current width for the component's lifetime — and
89
- * commit to native scrollback as exact, audited content. Rows at/after the
90
- * boundary repaint in place inside the visible window; when they scroll above
91
- * the window top they normally commit as frozen visual snapshots.
92
- *
93
- * A viewport-pinned region opts out of those mutable snapshot commits. Its
94
- * offscreen mutable rows are virtually clipped until the boundary advances;
95
- * use this for fixed-height dashboards whose frames replace each other rather
96
- * than append. A root that reports no seam commits everything that scrolls as
97
- * final (shell semantics).
98
- *
99
- * When several root children report a seam in the same frame, the topmost one
100
- * defines the boundary and pinning policy: commits are prefix-only, so
101
- * everything below the first seam is already excluded.
102
- */
103
- export interface NativeScrollbackLiveRegion {
104
- getNativeScrollbackLiveRegionStart(): number | undefined;
105
- /** Keeps the mutable suffix viewport-local instead of recording frozen snapshots. */
106
- isNativeScrollbackLiveRegionPinned?(): boolean;
107
- /**
108
- * Local row where viewport pinning begins. When omitted, pinning (if
109
- * reported) starts at {@link getNativeScrollbackLiveRegionStart}. A nested
110
- * transcript uses this to keep an earlier unpinned live seam while still
111
- * capping commits at a later pinned dashboard (hub wait, todo snapshot).
112
- */
113
- getNativeScrollbackLiveRegionPinnedStart?(): number | undefined;
114
- }
115
- export interface NativeScrollbackCommittedRows {
116
- setNativeScrollbackCommittedRows(rows: number): void;
117
- }
118
- /**
119
- * Width-independent source boundary for multiplexer resize epochs. Capture
120
- * reads the last rendered source state; resolve maps that same logical boundary
121
- * into the most recent render's physical rows at its new width. The current
122
- * boundary identifies the source tail after updates queued during the resize.
123
- */
124
- export interface NativeScrollbackWidthEpoch {
125
- captureNativeScrollbackWidthEpoch(): unknown;
126
- resolveNativeScrollbackWidthEpoch(boundary: unknown): number | undefined;
127
- getNativeScrollbackWidthEpochRows(): number | undefined;
128
- /** False when updates can insert before captured trailing rows. */
129
- isNativeScrollbackWidthEpochAppendOnly?(boundary: unknown): boolean;
130
- /** Changes when child structure mutates independently of width reflow. */
131
- getNativeScrollbackWidthEpochRevision?(): number;
132
- }
133
- /**
134
- * A component that discards rows after they enter native scrollback implements
135
- * this hook so a destructive full replay can rehydrate its complete frame.
136
- */
137
- export interface NativeScrollbackReplay {
138
- prepareNativeScrollbackReplay(): void;
139
- }
140
- /**
141
- * Opt-in stability report for components that mutate their returned render
142
- * array in place across frames (instead of returning a fresh array per
143
- * change). The engine reads it right after the component's `render()` returns:
144
- * the report counts the leading rows of the just-returned array that are
145
- * byte-identical to the array state the reader last observed. The engine uses
146
- * it to reuse the composed frame's prefix — skipping marker extraction, line
147
- * preparation, and the committed-prefix audit for those rows.
148
- *
149
- * Contract:
150
- * - Reading CONSUMES the report: it re-bases the baseline to the current
151
- * array state. The accumulated count therefore covers every render since
152
- * the previous read, so out-of-band `render()` calls between engine frames
153
- * (an exporter walking the tree) can only lower the report, never inflate
154
- * it past what the engine actually has.
155
- * - An implementer that cannot prove stability for a frame must lower the
156
- * accumulated count to 0 for that render.
157
- * - Rows at or beyond the report may have been mutated in place; rows before
158
- * it must be the identical string values at the identical indices.
159
- */
160
- export interface RenderStablePrefix {
161
- getRenderStablePrefixRows(): number;
162
- }
163
- /**
164
- * Opt-in fast path for composing only the visible tail of a tall component
165
- * during a terminal resize. A drag emits a SIGWINCH burst, and the width
166
- * changes on every event: a full compose re-lays-out (and, for markdown,
167
- * re-lexes) the entire transcript per event — O(history) work that is
168
- * discarded the instant the next event arrives. While the resize is in flight
169
- * the engine paints only the viewport, so it asks each tall root child for at
170
- * most `maxRows` rows from the bottom of its render at `width` and skips
171
- * composing everything above the fold. The authoritative full paint replays
172
- * once the drag settles (see {@link TUI} resize handling).
173
- *
174
- * Contract:
175
- * - Returns the BOTTOM rows of the component's full render at `width`, in
176
- * top-to-bottom order, capped at `maxRows` (fewer when the component is
177
- * shorter). The rows MUST be byte-identical to the corresponding tail of
178
- * what `render(width)` would have returned, modulo a one-row separator at
179
- * the very top edge (a transient frame the settle paint overwrites).
180
- * - MUST NOT mutate any persistent full-compose state: the next `render()`
181
- * (the settle paint) has to reconcile exactly as if the tail render never
182
- * happened. Warming pure per-width render caches is fine and desirable.
183
- */
184
- export interface ViewportTailProvider {
185
- renderViewportTail(width: number, maxRows: number): readonly string[];
186
- }
187
108
  /**
188
109
  * Interface for components that can receive focus and display a cursor.
189
110
  * When focused, the component should emit CURSOR_MARKER at the cursor position
@@ -207,25 +128,12 @@ export interface RenderRequestOptions {
207
128
  clearScrollback?: boolean;
208
129
  }
209
130
  /**
210
- * What a settled in-place width resize (multiplexer pane or an in-place-latched
211
- * direct terminal) does to native scrollback, which the host rewrapped at the
212
- * old width:
213
- * - `append`: replay the transcript at the current width below the old-wrap
214
- * history — one fresh copy per settled resize, nothing destroyed.
215
- * - `rebuild`: clear native history first (ED3) and replay — history holds the
216
- * transcript exactly once at the current width. Requires a host that honors
217
- * an inner ED3 (tmux does; GNU screen ignores it, degrading to `append`),
218
- * and erases pre-session pane history.
219
- * - `preserve`: repaint the viewport only — zero history growth; scrollback
220
- * keeps the old-width wrap until content next scrolls off.
131
+ * Controls how a settled terminal resize refreshes native history.
221
132
  *
222
- * The raw engine defaults to `preserve` (append-only native scrollback, the
223
- * engine's baseline contract); `PI_TUI_RESIZE_SCROLLBACK` overrides that
224
- * initial value. The coding agent applies its `tui.resizeScrollback` setting
225
- * (default `append`) on top at startup, so interactive sessions refresh
226
- * stale-width history out of the box.
133
+ * `append` replays the current transcript below retained history, `rebuild`
134
+ * clears history before replaying it, and `preserve` repaints only the viewport.
227
135
  */
228
- export type ResizeScrollbackMode = "rebuild" | "append" | "preserve";
136
+ export type ResizeScrollbackMode = "append" | "rebuild" | "preserve";
229
137
  /** Type guard to check if a component implements Focusable */
230
138
  export declare function isFocusable(component: Component | null): component is Component & Focusable;
231
139
  /**
@@ -309,7 +217,7 @@ export interface OverlayHandle {
309
217
  /**
310
218
  * Container - a component that contains other components
311
219
  */
312
- export declare class Container implements Component, NativeScrollbackCommittedRows, NativeScrollbackReplay, NativeScrollbackWidthEpoch {
220
+ export declare class Container implements Component {
313
221
  #private;
314
222
  children: Component[];
315
223
  setIgnoreTight(ignore: boolean): this;
@@ -325,21 +233,6 @@ export declare class Container implements Component, NativeScrollbackCommittedRo
325
233
  * {@link clear} for that). Idempotent per child via each child's own dispose.
326
234
  */
327
235
  dispose(): void;
328
- /**
329
- * Split the committed prefix from the container's most recently rendered
330
- * rows across its children. The memoized child arrays are the exact geometry
331
- * that produced that frame; when the child list was invalidated or rebuilt,
332
- * there is no safe old-to-new coordinate mapping, so propagation waits for
333
- * the next render/post-emit publication.
334
- */
335
- setNativeScrollbackCommittedRows(rows: number): void;
336
- /** Recursively discard layout locks that are meaningful only to the old tape. */
337
- prepareNativeScrollbackReplay(): void;
338
- captureNativeScrollbackWidthEpoch(): unknown;
339
- resolveNativeScrollbackWidthEpoch(boundary: unknown): number | undefined;
340
- getNativeScrollbackWidthEpochRows(): number | undefined;
341
- isNativeScrollbackWidthEpochAppendOnly(boundary: unknown): boolean;
342
- getNativeScrollbackWidthEpochRevision(): number;
343
236
  render(width: number): readonly string[];
344
237
  }
345
238
  /**
@@ -349,41 +242,6 @@ export declare class Container implements Component, NativeScrollbackCommittedRo
349
242
  * merges, so SGR-light lines incur only a single `indexOf` scan.
350
243
  */
351
244
  export declare function coalesceAdjacentSgr(line: string): string;
352
- /**
353
- * Decide whether `frame` still aligns with the committed prefix, and where to
354
- * re-anchor the commit index when it does not. Returns the resync row index,
355
- * or -1 when no resync is needed.
356
- *
357
- * Zones (verifiedTo ≤ finalTo ≤ prefix.length):
358
- * [0, verifiedTo) VERIFIED exact rows — sampled with tolerance.
359
- * [verifiedTo, finalTo) NEWLY-FINAL rows — frozen visual snapshots whose
360
- * source just became declared-final (the block finalized / a barrier
361
- * cleared). Hard-scanned in FULL with no tolerance: any content change
362
- * (a pending header settling, a preview replaced by its result, a tail
363
- * shifting up after a barrier removal) re-anchors so the engine can
364
- * erase-and-replay history with the final content exactly once (or, on
365
- * ED3-unsafe multiplexers, recommit it below the frozen snapshot —
366
- * duplication, never loss) instead of committing it nowhere and
367
- * painting it nowhere.
368
- * [finalTo, prefix.length) FROZEN visual snapshots of still-live rows —
369
- * exempt: their drift is expected (a collapsing preview, a ticking
370
- * progress tree) and must never spray re-anchors mid-run.
371
- *
372
- * The verified zone's sampled check exploits the asymmetry between the two
373
- * mutation classes: an in-place edit/restyle disturbs only the touched rows
374
- * (alignment below stays intact; the stale copy in history is the accepted
375
- * artifact), while an insertion/deletion shifts EVERY row below it. Up to 8
376
- * non-blank rows within the last 24 verified rows are compared SGR-stripped
377
- * (theme changes stay quiet), tolerating a SINGLE mismatch. The tolerance is
378
- * load-bearing for roots that report NO seam: an animated row already in
379
- * history would otherwise re-anchor on every glyph tick.
380
- *
381
- * Highly repetitive tails (identical filler rows) can mask a shift in the tail
382
- * sample, in which case the skipped rows are content-identical to the committed
383
- * ones — observationally harmless. Exported for the render-stress harness, whose
384
- * shadow commit ledger must mirror the engine's law exactly.
385
- */
386
- export declare function findCommittedPrefixResync(frame: readonly string[], prefix: readonly string[], verifiedTo?: number, finalTo?: number): number;
387
245
  /**
388
246
  * TUI - Main class for managing terminal UI with differential rendering
389
247
  */
@@ -399,19 +257,9 @@ export declare class TUI extends Container {
399
257
  hidden: boolean;
400
258
  }[];
401
259
  constructor(terminal: Terminal, showHardwareCursor?: boolean, options?: TUIOptions);
402
- captureNativeScrollbackWidthEpoch(): unknown;
403
- resolveNativeScrollbackWidthEpoch(boundary: unknown): number | undefined;
404
- getNativeScrollbackWidthEpochRows(): number | undefined;
405
- render(width: number): readonly string[];
260
+ /** Install the product-owned bounded frame provider. */
261
+ setFrameProvider(provider: TerminalFrameProvider | undefined): void;
406
262
  get fullRedraws(): number;
407
- /**
408
- * Transient viewport-only paints emitted by the non-multiplexer resize fast
409
- * path. These never touch native scrollback or the commit ledger, so they
410
- * are counted apart from {@link fullRedraws}.
411
- */
412
- get resizeViewportPaints(): number;
413
- /** Whether a non-multiplexer resize drag is currently in flight. */
414
- get resizeViewportActive(): boolean;
415
263
  /** Shared budget that caps how many inline images render as live graphics. */
416
264
  get imageBudget(): ImageBudget;
417
265
  /**
@@ -420,29 +268,12 @@ export declare class TUI extends Container {
420
268
  * plus a full redraw on the frame after a new image exceeds the cap.
421
269
  */
422
270
  setMaxInlineImages(cap: number): void;
423
- /** Delete every tracked Kitty image from the terminal graphics store. */
424
- clearInlineImages(): void;
425
- /**
426
- * Get whether scrollback divergence rebuild is enabled.
427
- */
428
- getScrollbackRebuild(): boolean;
429
- /**
430
- * Enable or disable scrollback divergence rebuild (default off).
431
- * When enabled, the engine will erase and replay the terminal's
432
- * scrollback (using ED3 / alt buffer / scrollback replay) to avoid
433
- * duplicate blocks when a block's final form replaces its live preview.
434
- */
435
- setScrollbackRebuild(enabled: boolean): void;
436
- /**
437
- * Get how a settled in-place width resize refreshes native scrollback.
438
- */
271
+ /** Return how settled resizes refresh native scrollback. */
439
272
  getResizeScrollback(): ResizeScrollbackMode;
440
- /**
441
- * Set how a settled in-place width resize refreshes native scrollback
442
- * (see {@link ResizeScrollbackMode}; engine default `preserve` — the coding
443
- * agent applies its `tui.resizeScrollback` setting, default `append`).
444
- */
273
+ /** Set how settled resizes refresh native scrollback. */
445
274
  setResizeScrollback(mode: ResizeScrollbackMode): void;
275
+ /** Delete every tracked Kitty image from the terminal graphics store. */
276
+ clearInlineImages(): void;
446
277
  getShowHardwareCursor(): boolean;
447
278
  setShowHardwareCursor(enabled: boolean): void;
448
279
  /**
@@ -478,53 +309,24 @@ export declare class TUI extends Container {
478
309
  removeInputListener(listener: InputListener): void;
479
310
  stop(): void;
480
311
  /**
481
- * Force an immediate full replay of the current frame, including native
482
- * scrollback. This is the keyboard-accessible equivalent of the resize reset:
483
- * no queued diff frame or terminal scrollback probe can downgrade it to a
484
- * viewport-only repaint.
485
- *
486
- * Invalidates every component first so the replay reflects current state. A
487
- * geometry-driven reset thaws frozen scrollback snapshots implicitly (the new
488
- * width misses every cached snapshot), but a same-width reset would otherwise
489
- * replay stale snapshots — leaving host-frozen blocks (e.g. a transcript whose
490
- * committed rows are immutable on ED3-risk terminals) showing pre-mutation
491
- * content. Invalidation is the generic signal those containers use to retire
492
- * their snapshots, which is exactly what a user-driven display reset wants.
312
+ * Destructive user-gesture reset: invalidate every component, erase native
313
+ * history, then repaint from row zero. Reachable only from explicit gestures (session
314
+ * replace, /tree, an explicit clear) never from ordinary rendering,
315
+ * animation, resize, or finalization.
493
316
  */
494
317
  resetDisplay(): void;
495
318
  requestRender(force?: boolean, options?: RenderRequestOptions): void;
496
319
  /**
497
- * Opt `component` into subtree-only renders when input leaves focus stable.
498
- *
499
- * The host must explicitly request renders for every sibling mutated by the
500
- * component's input callbacks. Components without this opt-in retain the
501
- * legacy full-root render after input.
320
+ * Paint a forced frame synchronously when startup must hand off an already
321
+ * visible component tree before further async initialization. Same as
322
+ * {@link requestRender} minus the `setImmediate` hop.
502
323
  */
503
- enableScopedInputRender(component: Component): void;
324
+ renderNow(options?: RenderRequestOptions): void;
504
325
  /**
505
326
  * Schedule a render on behalf of `component` after a self-contained change
506
- * (spinner frame, blink) that cannot have affected any other component.
507
- *
508
- * When every request since the last frame is component-scoped and the
509
- * frame is otherwise quiet — no resize or geometry change, no overlays, no
510
- * live inline images, no forced repaint, unchanged root child list — the
511
- * next compose re-renders only the root subtrees containing the requesting
512
- * components and reuses the previous frame's rows (and seam reports) for
513
- * every other root child, skipping the full component-tree walk that makes
514
- * long transcripts expensive to repaint at animation rate. Any concurrent
515
- * full request or unsafe condition downgrades the frame to a normal full
516
- * compose, so this is never less correct than `requestRender()` — only
517
- * cheaper.
518
- */
519
- requestComponentRender(component: Component): void;
520
- /**
521
- * Rewrite a quiet, visible component segment directly.
522
- *
523
- * Loader-style animation changes one already-positioned segment at a fixed
524
- * size. When the current frame geometry is still valid, rewrite just those
525
- * rows and update the diff baseline instead of scheduling a full render
526
- * cycle. Unsafe states fall back to `requestComponentRender()`, preserving
527
- * the ordinary renderer as the correctness path.
327
+ * (spinner frame, blink). Frames always compose the bounded viewport from
328
+ * scratch — retired blocks no longer render — so a scoped request is simply
329
+ * an ordinary render.
528
330
  */
529
- requestDirectWrite(component: Component): void;
331
+ requestComponentRender(_component: Component): void;
530
332
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "18.0.0",
4
+ "version": "18.0.3",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@oh-my-pi/pi-natives": "18.0.0",
41
- "@oh-my-pi/pi-utils": "18.0.0"
40
+ "@oh-my-pi/pi-natives": "18.0.3",
41
+ "@oh-my-pi/pi-utils": "18.0.3"
42
42
  },
43
43
  "devDependencies": {
44
44
  "kitty-vt-wasm": "^0.2.0"