@oh-my-pi/pi-tui 17.4.2 → 18.0.1

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,73 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.0.1] - 2026-08-23
6
+
7
+ ### Added
8
+
9
+ - Collapsed individual skill commands into a `/skill:` namespace entry to declutter suggestions
10
+ - Added `TUI.renderNow()` for terminal-safe synchronous priority frames that retain resize debounce, output-backlog, and image deferral safeguards.
11
+
12
+ ### Changed
13
+
14
+ - Improved slash command autocompletion to chain suggestions after selecting a namespace
15
+ - Replaced the native-scrollback inference API (`NativeScrollback*` interfaces and the scrollback rebuild/resize settings hooks) with explicit `TerminalFramePlan` history batches.
16
+ - 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.
17
+ - 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.
18
+
19
+ ### Fixed
20
+
21
+ - Fixed the resize live-region erase wiping visible history rows when the terminal height grows: cursor-relative addressing tracks the reflowed viewport top on grow, while the bottom-preserving bound still guards height shrinks.
22
+ - Fixed consecutive prompt submissions being skipped by persistent history, allowing the latest project metadata to replace the previous entry without duplicating editor navigation history.
23
+ - 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.
24
+ - 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)).
25
+ - Fixed stray characters appearing in the terminal viewport during title updates
26
+ - Fixed editor input lag when autocomplete providers are slow by keeping only the latest pending lookup.
27
+ - 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.
28
+ - Fixed Kitty OSC 66 headings activating before the host explicitly enables text sizing.
29
+ - 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)).
30
+
31
+ ## [18.0.0] - 2026-08-22
32
+
33
+ ### Breaking Changes
34
+
35
+ - Changed native macOS spelling and completion functions to return Promises.
36
+ - Updated `EditorTextAssistProvider.tryAutocorrect` signature to receive editor state instead of raw text.
37
+ - Updated `Editor.decorateText` signature to provide line and column context instead of raw text.
38
+
39
+ ### Added
40
+
41
+ - Added `EditorTextAssistProvider` with spelling suggestion support (`ctrl+.`), word replacement choices, and async autocorrection handling.
42
+ - Added `Terminal.pendingOutputBytes` and an output-backpressure render gate to drop stale frames on slow terminals.
43
+ - Added `deferInput` startup option and `enableInput()` across `Terminal`, `TUI`, and `TUIStartOptions` to improve startup responsiveness.
44
+ - Added icon support and customizable theming to autocomplete and select lists.
45
+ - Added `MarkdownTheme.createHighlightStream` for incremental syntax highlighting of completed lines in streaming Markdown code blocks.
46
+ - Added `maxDescriptionRows` option to `SelectList` layouts to truncate wrapped descriptions with an ellipsis.
47
+ - Added `commandUsage` ranking callback support to `CombinedAutocompleteProvider` to prioritize frequently used slash commands.
48
+ - Added `Editor.viewportRowsProvider` to constrain autocomplete dropdowns within the available terminal height.
49
+ - Added `Editor.setTheme()` to dynamically change themes without recreating the editor or losing draft content.
50
+
51
+ ### Changed
52
+
53
+ - Adjusted word completion to skip appending a trailing space when the following character is punctuation.
54
+ - Increased default autocomplete dropdown height from 5 to 10 items.
55
+
56
+ ### Fixed
57
+
58
+ - Fixed TUI freezing during large repaints on slow or occluded terminals by moving stdout writes to an off-thread writer.
59
+
60
+ ## [17.4.4] - 2026-08-22
61
+
62
+ ### Added
63
+
64
+ - Added `setResizeScrollback()` / `ResizeScrollbackMode` (`PI_TUI_RESIZE_SCROLLBACK` env initializer) controlling what a settled in-place width resize does to native scrollback, which the host rewraps naively at the old width: `append` replays the transcript at the settled width below the old-wrap history, `rebuild` clears pane history first (ED3) so it holds exactly one current-width copy, and `preserve` repaints the viewport only with zero history growth. The raw engine defaults to `preserve`; the coding agent's `tui.resizeScrollback` setting (default `append`) governs interactive sessions.
65
+
66
+ ### Fixed
67
+
68
+ - `visibleWidth` now measures APC sequences (Kitty graphics commands, cursor markers) as zero cells instead of counting their payload as printable text, matching the native width engine.
69
+ - Kitty Unicode-placeholder rows with long styled prefixes (e.g. bordered thumbnail cards) are recognized as image lines again, keeping them on the verbatim render path instead of SGR coalescing/truncation.
70
+ - Fixed multiplexer width-epoch resolution failing for every real component tree, which forced the conservative full-transcript replay (and one duplicated transcript copy in pane history) on every settled width resize: leading children without a width-epoch revision are no longer validated by width-dependent row counts (reflow is not mutation — identity plus the revision, when reported, is the stability proof), and `Markdown` now reports a width-independent mutation revision so it can sit above an epoch source ([#8193](https://github.com/can1357/oh-my-pi/issues/8193), [#7026](https://github.com/can1357/oh-my-pi/issues/7026)).
71
+
5
72
  ## [17.4.2] - 2026-08-21
6
73
 
7
74
  ### Added
package/README.md CHANGED
@@ -539,7 +539,7 @@ interface Terminal {
539
539
  **Built-in implementations:**
540
540
 
541
541
  - `ProcessTerminal` - Uses `process.stdin/stdout`
542
- - `VirtualTerminal` - For testing (uses ghostty-web)
542
+ - `VirtualTerminal` - For testing (uses kitty-vt-wasm)
543
543
 
544
544
  ## Utilities
545
545
 
@@ -10,6 +10,8 @@ export interface AutocompleteItem {
10
10
  value: string;
11
11
  label: string;
12
12
  description?: string;
13
+ /** Optional type-indicator glyph rendered in an aligned column before the label */
14
+ icon?: string;
13
15
  /** Dim hint text shown inline after cursor when this item is selected */
14
16
  hint?: string;
15
17
  }
@@ -18,6 +20,8 @@ export interface SlashCommand {
18
20
  name: string;
19
21
  aliases?: string[];
20
22
  description?: string;
23
+ /** Optional type-indicator glyph shown before the command name in autocomplete */
24
+ icon?: string;
21
25
  argumentHint?: string;
22
26
  /** Whether the command consumes argument text after the command name. False means the full input stays normal prompt text once args are present. */
23
27
  allowArgs?: boolean;
@@ -28,8 +32,8 @@ export interface SlashCommand {
28
32
  getInlineHint?(argumentText: string): string | null;
29
33
  }
30
34
  export interface AutocompleteProvider {
31
- /** Get autocomplete suggestions for current text/cursor position */
32
- 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<{
33
37
  items: AutocompleteItem[];
34
38
  prefix: string;
35
39
  } | null>;
@@ -64,8 +68,9 @@ export interface AutocompleteProvider {
64
68
  * Force file-path completion (called on Tab). Returns matched items plus the
65
69
  * full prefix, or null when no path token sits before the cursor. Present on
66
70
  * file-aware providers; absent on slash-only ones.
71
+ * Expensive providers SHOULD stop when `signal` aborts.
67
72
  */
68
- getForceFileSuggestions?(lines: string[], cursorLine: number, cursorCol: number): Promise<{
73
+ getForceFileSuggestions?(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
69
74
  items: AutocompleteItem[];
70
75
  prefix: string;
71
76
  } | null>;
@@ -73,7 +78,13 @@ export interface AutocompleteProvider {
73
78
  shouldTriggerFileCompletion?(lines: string[], cursorLine: number, cursorCol: number): boolean;
74
79
  }
75
80
  type CommandEntry = SlashCommand | AutocompleteItem;
81
+ /** Optional behaviors for {@link CombinedAutocompleteProvider}. */
82
+ export interface CombinedAutocompleteOptions {
83
+ /** Usage count per command name; higher counts rank earlier among equal text-match scores. */
84
+ commandUsage?: (name: string) => number;
85
+ }
76
86
  export declare function scoreCommandTextMatch(lowerPrefix: string, lowerTarget: string): number;
87
+ export declare const SKILL_NAMESPACE = "skill:";
77
88
  /**
78
89
  * Whether a mid-prompt slash token (`prose … /tok`) is skill-shaped enough to
79
90
  * surface `name` in the skill popup. Deliberately stricter than submitted
@@ -90,8 +101,8 @@ export declare function scoreCommandTextMatch(lowerPrefix: string, lowerTarget:
90
101
  export declare function midPromptSkillTokenMatches(lowerToken: string, name: string, description?: string): boolean;
91
102
  export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
92
103
  #private;
93
- constructor(commands?: CommandEntry[], basePath?: string);
94
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
104
+ constructor(commands?: CommandEntry[], basePath?: string, options?: CombinedAutocompleteOptions);
105
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
95
106
  items: AutocompleteItem[];
96
107
  prefix: string;
97
108
  } | null>;
@@ -101,7 +112,7 @@ export declare class CombinedAutocompleteProvider implements AutocompleteProvide
101
112
  cursorCol: number;
102
113
  };
103
114
  invalidateDirCache(dir?: string): void;
104
- getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
115
+ getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number, signal?: AbortSignal): Promise<{
105
116
  items: AutocompleteItem[];
106
117
  prefix: string;
107
118
  } | null>;
@@ -23,6 +23,38 @@ interface HistoryStorage {
23
23
  add(prompt: string, cwd?: string): Promise<void>;
24
24
  getRecent(limit: number): HistoryEntry[];
25
25
  }
26
+ /** A synchronous replacement immediately before the editor cursor. */
27
+ export interface EditorInlineReplacement {
28
+ /** UTF-16 code units to remove immediately before the cursor. */
29
+ replaceLen: number;
30
+ /** Literal text inserted where the removed suffix started. */
31
+ insert: string;
32
+ }
33
+ /** Replacement candidates and the current-line span they replace. */
34
+ export interface EditorWordReplacements {
35
+ line: number;
36
+ startCol: number;
37
+ endCol: number;
38
+ items: readonly string[];
39
+ }
40
+ /** Source location for one visual text segment passed to `decorateText`. */
41
+ export interface EditorTextDecorationContext {
42
+ line: number;
43
+ startCol: number;
44
+ endCol: number;
45
+ }
46
+ /**
47
+ * Optional prose assistance kept separate from command/file autocomplete.
48
+ * Hosts independently decide whether word completion and autocorrection are enabled.
49
+ */
50
+ export interface EditorTextAssistProvider {
51
+ /** Return ghost-text suffix for the partial word at the cursor, or `null`. */
52
+ getWordCompletion?(lines: string[], cursorLine: number, cursorCol: number): string | null;
53
+ /** Return a correction after one single-character insertion, or `null`. */
54
+ tryAutocorrect?(lines: string[], cursorLine: number, cursorCol: number): EditorInlineReplacement | null | Promise<EditorInlineReplacement | null>;
55
+ /** Return replacement candidates for the misspelled word at the cursor. */
56
+ getWordReplacements?(lines: string[], cursorLine: number, cursorCol: number): EditorWordReplacements | null | Promise<EditorWordReplacements | null>;
57
+ }
26
58
  export declare class Editor implements Component, Focusable {
27
59
  #private;
28
60
  /** Focusable interface - set by TUI when focus changes */
@@ -34,9 +66,13 @@ export declare class Editor implements Component, Focusable {
34
66
  /** Optional hook that decorates displayed user text after source-text layout.
35
67
  * Width-changing output is allowed on lines without the cursor; it is truncated
36
68
  * to the content width rather than reflowed. Cursor glyphs and inline hints are excluded. */
37
- decorateText: ((text: string) => string) | undefined;
69
+ decorateText: ((text: string, context: EditorTextDecorationContext) => string) | undefined;
38
70
  borderColor: (str: string) => string;
39
71
  onAutocompleteUpdate?: () => void;
72
+ /** Called after an async text-assist result mutates the document outside an input event, so hosts can schedule a repaint. */
73
+ onTextAssistApplied?: () => void;
74
+ /** Terminal height source for clamping the autocomplete dropdown. Hosts wire this to their Terminal's rows. */
75
+ viewportRowsProvider?: () => number;
40
76
  /** Optional pattern matching atomic placeholder tokens (e.g. `[Image #1, 800x600]` or
41
77
  * `[Paste #2, +30 lines]`) that the editor treats as indivisible: a backspace or forward-delete
42
78
  * landing on any character of a token removes the whole token instead of corrupting it into
@@ -56,7 +92,10 @@ export declare class Editor implements Component, Focusable {
56
92
  onAutocompleteCancel?: () => void;
57
93
  disableSubmit: boolean;
58
94
  constructor(theme: EditorTheme);
95
+ setTheme(theme: EditorTheme): void;
59
96
  setAutocompleteProvider(provider: AutocompleteProvider): void;
97
+ /** Install prose assistance without changing command/file autocomplete. */
98
+ setTextAssistProvider(provider: EditorTextAssistProvider | undefined): void;
60
99
  /**
61
100
  * Set custom content for the top border (e.g., status line).
62
101
  * Pass undefined to use the default plain border.
@@ -75,9 +114,7 @@ export declare class Editor implements Component, Focusable {
75
114
  * Use this when the top border derives from state that mutates far faster
76
115
  * than the render cadence (session events, streaming, subagent updates).
77
116
  * The TUI already throttles renders, so a provider is invoked exactly once
78
- * per frame and does no work between paints. Return a logical `revision` to
79
- * distinguish concurrent status mutations from pure width reflow.
80
- */
117
+ * per frame and does no work between paints. */
81
118
  setTopBorderProvider(provider: ((availableWidth: number) => EditorTopBorder | undefined) | undefined): void;
82
119
  /**
83
120
  * Show or hide the editor border chrome.
@@ -106,6 +143,7 @@ export declare class Editor implements Component, Focusable {
106
143
  setPaddingX(paddingX: number): void;
107
144
  getAutocompleteMaxVisible(): number;
108
145
  setAutocompleteMaxVisible(maxVisible: number): void;
146
+ /** Loads persistent prompts for navigation and enables future persistence. */
109
147
  setHistoryStorage(storage: HistoryStorage): void;
110
148
  /**
111
149
  * Add a prompt to history for up/down arrow navigation.
@@ -116,7 +154,6 @@ export declare class Editor implements Component, Focusable {
116
154
  render(width: number): readonly string[];
117
155
  handleInput(data: string): void;
118
156
  getText(): string;
119
- getNativeScrollbackWidthEpochRevision(): number;
120
157
  /** Whether the buffer text equals `value`, without `getText()`'s full join —
121
158
  * O(1) for the hot per-keystroke probes against short single-line values. */
122
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. */
@@ -26,6 +26,14 @@ export interface DefaultTextStyle {
26
26
  /** Underline text */
27
27
  underline?: boolean;
28
28
  }
29
+ /**
30
+ * Stateful incremental code highlighter carrying parser state across pushes.
31
+ * Produced per streaming fence by {@link MarkdownTheme.createHighlightStream}.
32
+ */
33
+ export interface HighlightStreamSession {
34
+ /** Highlight the next chunk and advance parser state. */
35
+ push(chunk: string): string;
36
+ }
29
37
  /**
30
38
  * Theme functions for markdown elements.
31
39
  * Each function takes text and returns styled text with ANSI codes.
@@ -46,6 +54,14 @@ export interface MarkdownTheme {
46
54
  strikethrough: (text: string) => string;
47
55
  underline: (text: string) => string;
48
56
  highlightCode?: (code: string, lang?: string) => string[];
57
+ /**
58
+ * Create a stateful incremental highlighter for one streaming code fence.
59
+ * `push` receives newline-terminated complete lines (only the final push
60
+ * may omit the trailing newline) and must return highlighted ANSI text for
61
+ * exactly the pushed chunk, byte-identical to highlighting the concatenated
62
+ * text through `highlightCode`. Return null when `lang` is unsupported.
63
+ */
64
+ createHighlightStream?: (lang?: string) => HighlightStreamSession | null;
49
65
  /**
50
66
  * Resolve a mermaid ASCII rendering by fenced block source text.
51
67
  * Return null to fall back to fenced code rendering.
@@ -53,7 +69,7 @@ export interface MarkdownTheme {
53
69
  resolveMermaidAscii?: (source: string, maxWidth?: number) => string | null;
54
70
  symbols: SymbolTheme;
55
71
  }
56
- export declare class Markdown implements Component, NativeScrollbackCommittedRows, NativeScrollbackReplay, NativeScrollbackWidthEpoch {
72
+ export declare class Markdown implements Component {
57
73
  #private;
58
74
  setIgnoreTight(ignore: boolean): this;
59
75
  constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
@@ -61,28 +77,6 @@ export declare class Markdown implements Component, NativeScrollbackCommittedRow
61
77
  invalidate(): void;
62
78
  get transientRenderCache(): boolean;
63
79
  set transientRenderCache(value: boolean);
64
- /**
65
- * Rows at the top of the most recent render() (top padding + rendered
66
- * frozen-token prefix) whose bytes are settled: byte-stable at this
67
- * width/theme for as long as the text keeps growing append-only. Hosts
68
- * feed this to transcript commit gating (see the coding agent's
69
- * `FinalizableBlock.getTranscriptBlockSettledRows`). 0 outside streaming
70
- * (`transientRenderCache`) mode, after a text rewind (re-earned on the new
71
- * lineage), and on cache-served non-streaming renders.
72
- */
73
- getLastRenderSettledRows(): number;
74
- captureNativeScrollbackWidthEpoch(): unknown;
75
- resolveNativeScrollbackWidthEpoch(boundary: unknown): number | undefined;
76
- getNativeScrollbackWidthEpochRows(): number | undefined;
77
- isNativeScrollbackWidthEpochAppendOnly(boundary: unknown): boolean;
78
- /**
79
- * Freeze every table whose first physical row is already part of the native
80
- * scrollback prefix. The recorded widths came from the exact frame that was
81
- * just emitted, so the next streamed delta cannot retroactively widen it.
82
- */
83
- setNativeScrollbackCommittedRows(rows: number): void;
84
- /** A destructive replay removes the immutable tape this layout was guarding. */
85
- prepareNativeScrollbackReplay(): void;
86
80
  render(width: number): readonly string[];
87
81
  }
88
82
  /**
@@ -5,6 +5,8 @@ export interface SelectItem {
5
5
  value: string;
6
6
  label: string;
7
7
  description?: string;
8
+ /** Optional type-indicator glyph rendered in an aligned column before the label */
9
+ icon?: string;
8
10
  /** Dim hint text shown inline after cursor when this item is selected */
9
11
  hint?: string;
10
12
  }
@@ -15,6 +17,8 @@ export interface SelectListTheme {
15
17
  scrollInfo: (text: string) => string;
16
18
  noMatch: (text: string) => string;
17
19
  symbols: SymbolTheme;
20
+ /** Style for the type-icon column on unselected rows. Defaults to plain text. */
21
+ icon?: (text: string) => string;
18
22
  /** Hover band applied to the full row under the mouse pointer. */
19
23
  hovered?: (text: string) => string;
20
24
  }
@@ -39,6 +43,11 @@ export interface SelectListLayoutOptions {
39
43
  * wrap unevenly.
40
44
  */
41
45
  wrapDescription?: boolean;
46
+ /**
47
+ * Cap wrapped descriptions at this many visual rows; the last kept row is
48
+ * ellipsized. Only meaningful with `wrapDescription`.
49
+ */
50
+ maxDescriptionRows?: number;
42
51
  }
43
52
  export declare class SelectList implements Component, MouseRoutable {
44
53
  #private;
@@ -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
@@ -25,6 +25,7 @@ export interface Keybindings {
25
25
  "tui.editor.yank": true;
26
26
  "tui.editor.yankPop": true;
27
27
  "tui.editor.undo": true;
28
+ "tui.editor.spellingSuggestions": true;
28
29
  "tui.input.newLine": true;
29
30
  "tui.input.submit": true;
30
31
  "tui.input.tab": true;
@@ -129,6 +130,10 @@ export declare const TUI_KEYBINDINGS: {
129
130
  readonly defaultKeys: ["ctrl+-", "ctrl+_"];
130
131
  readonly description: "Undo";
131
132
  };
133
+ readonly "tui.editor.spellingSuggestions": {
134
+ readonly defaultKeys: "ctrl+.";
135
+ readonly description: "Show spelling replacements";
136
+ };
132
137
  readonly "tui.input.newLine": {
133
138
  readonly defaultKeys: ["shift+enter", "ctrl+j"];
134
139
  readonly description: "Insert newline";
@@ -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
@@ -58,10 +68,30 @@ export declare function setAltScreenActive(active: boolean): void;
58
68
  export declare function emergencyTerminalRestore(): void;
59
69
  /** Terminal-reported appearance (dark/light mode). */
60
70
  export type TerminalAppearance = "dark" | "light";
71
+ /** Options for {@link Terminal.start}. */
72
+ export interface TerminalStartOptions {
73
+ /**
74
+ * Paint-only start: skip raw mode, stdin ownership, and every probe that
75
+ * elicits a response on stdin. The host tty keeps cooked-mode line editing
76
+ * (kernel echo lands at the hardware cursor), and typed bytes stay queued
77
+ * in the kernel until {@link Terminal.enableInput} takes ownership and
78
+ * replays them through `onInput`. Used for the startup prepaint so typing
79
+ * echoes even while module loading blocks the event loop.
80
+ */
81
+ deferInput?: boolean;
82
+ }
61
83
  /** Identity of an accepted explicit terminal appearance refresh request. */
62
84
  export type TerminalAppearanceRequestToken = number;
63
85
  export interface Terminal {
64
- start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void): void;
86
+ start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
87
+ /**
88
+ * Take ownership of stdin after a `deferInput` start: enable raw mode,
89
+ * attach input handlers, and run the capability probes start() skipped.
90
+ * Bytes the user typed in cooked mode meanwhile are replayed through
91
+ * `onInput`. No-op when input was never deferred. Optional so custom
92
+ * Terminals built against older pi-tui versions keep working.
93
+ */
94
+ enableInput?(): void;
65
95
  stop(): void;
66
96
  /**
67
97
  * Drain stdin before exiting to prevent Kitty key release events from
@@ -73,6 +103,14 @@ export interface Terminal {
73
103
  write(data: string): void;
74
104
  get columns(): number;
75
105
  get rows(): number;
106
+ /**
107
+ * Output bytes accepted but not yet delivered to the terminal, when the
108
+ * implementation can report it. The renderer skips composing new frames
109
+ * while this backlog is deep, so a slow terminal receives only fresh
110
+ * frames instead of a queue of stale ones. Optional so custom Terminals
111
+ * built against older pi-tui versions keep working.
112
+ */
113
+ readonly pendingOutputBytes?: number;
76
114
  get kittyProtocolActive(): boolean;
77
115
  get kittyEnableSequence(): string | null;
78
116
  readonly keyboardEnhancementEnterSequence?: string | null;
@@ -158,11 +196,13 @@ export declare class ProcessTerminal implements Terminal {
158
196
  */
159
197
  refreshAppearance(requestToken?: TerminalAppearanceRequestToken): TerminalAppearanceRequestToken | void;
160
198
  onPrivateModeReport(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void;
161
- start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void): void;
199
+ start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
200
+ enableInput(): void;
162
201
  drainInput(maxMs?: number, idleMs?: number): Promise<void>;
163
202
  stop(): void;
164
203
  write(data: string): void;
165
204
  get columns(): number;
205
+ get pendingOutputBytes(): number;
166
206
  get rows(): number;
167
207
  moveBy(lines: number): void;
168
208
  hideCursor(force?: boolean): void;