@oh-my-pi/pi-tui 18.7.0 → 18.8.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +27 -52
  2. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  3. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  4. package/dist/types/chat/fence-figure.d.ts +42 -0
  5. package/dist/types/chat/svg-figure.d.ts +1 -1
  6. package/dist/types/chat/tool-execution.d.ts +2 -0
  7. package/dist/types/components/editor.d.ts +8 -3
  8. package/dist/types/components/markdown.d.ts +11 -6
  9. package/dist/types/fuzzy.d.ts +17 -3
  10. package/dist/types/native/backend.d.ts +2 -1
  11. package/dist/types/native/blobs.d.ts +4 -2
  12. package/dist/types/native/encode.d.ts +3 -4
  13. package/dist/types/overlays/agent-transcript-viewer.d.ts +8 -1
  14. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  15. package/dist/types/overlays/history-search.d.ts +10 -0
  16. package/dist/types/overlays/model-browser.d.ts +6 -0
  17. package/dist/types/prompt/custom-editor.d.ts +7 -4
  18. package/dist/types/prompt/interactive-context-helpers.d.ts +2 -1
  19. package/dist/types/tools/renderer.d.ts +22 -0
  20. package/dist/types/tools/streaming-output.d.ts +11 -1
  21. package/dist/types/tools/write.d.ts +8 -1
  22. package/package.json +9 -9
  23. package/src/apps/debug/log-viewer.ts +188 -72
  24. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  25. package/src/apps/debug/raw-sse.ts +39 -5
  26. package/src/apps/git/diff-pane.ts +26 -5
  27. package/src/autocomplete.ts +59 -33
  28. package/src/chat/assistant-message.ts +30 -18
  29. package/src/chat/fence-figure.ts +80 -0
  30. package/src/chat/svg-figure.ts +2 -4
  31. package/src/chat/tool-execution.ts +53 -4
  32. package/src/components/editor.ts +16 -3
  33. package/src/components/markdown.ts +80 -307
  34. package/src/fuzzy.ts +59 -14
  35. package/src/native/backend.ts +170 -15
  36. package/src/native/blobs.ts +4 -2
  37. package/src/native/encode.ts +58 -31
  38. package/src/overlays/agent-hub-projection.ts +70 -2
  39. package/src/overlays/agent-hub.ts +3 -2
  40. package/src/overlays/agent-transcript-viewer.ts +64 -13
  41. package/src/overlays/agents-hub.ts +14 -7
  42. package/src/overlays/ask-dialog.ts +6 -9
  43. package/src/overlays/copy-selector.ts +30 -8
  44. package/src/overlays/extensions/state-manager.ts +52 -12
  45. package/src/overlays/history-search.ts +100 -7
  46. package/src/overlays/jobs-panel.ts +30 -2
  47. package/src/overlays/model-browser.ts +107 -21
  48. package/src/overlays/model-hub.ts +34 -7
  49. package/src/overlays/oauth-selector.ts +41 -26
  50. package/src/overlays/plan-review-overlay.ts +142 -64
  51. package/src/overlays/rewind-selector.ts +86 -17
  52. package/src/overlays/session-selector.ts +24 -4
  53. package/src/overlays/settings-selector.ts +21 -6
  54. package/src/overlays/tree-selector.ts +20 -4
  55. package/src/overlays/usage-dashboard.ts +12 -15
  56. package/src/prompt/attachment-chips.ts +2 -1
  57. package/src/prompt/composer-attachments.ts +6 -4
  58. package/src/prompt/custom-editor.ts +48 -19
  59. package/src/prompt/interactive-context-helpers.ts +27 -18
  60. package/src/prompt/macos-spelling.ts +7 -0
  61. package/src/prompt/magic-keywords.ts +11 -2
  62. package/src/prompt/word-completion.ts +38 -5
  63. package/src/setup/scenes/splash.ts +109 -45
  64. package/src/status-line/component.ts +220 -64
  65. package/src/status-line/segments.ts +5 -2
  66. package/src/terminal.ts +3 -2
  67. package/src/tools/ast-edit.ts +59 -35
  68. package/src/tools/ast-grep.ts +35 -16
  69. package/src/tools/default-renderer.ts +35 -19
  70. package/src/tools/eval-format/index.ts +28 -2
  71. package/src/tools/eval-format/javascript.ts +7 -5
  72. package/src/tools/eval.ts +164 -78
  73. package/src/tools/grep.ts +81 -48
  74. package/src/tools/lsp.ts +81 -59
  75. package/src/tools/mcp.ts +98 -68
  76. package/src/tools/renderer.ts +27 -0
  77. package/src/tools/streaming-output.ts +120 -48
  78. package/src/tools/task.ts +111 -54
  79. package/src/tools/todo.ts +115 -29
  80. package/src/tools/wait.ts +34 -24
  81. package/src/tools/write.ts +77 -39
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.8.1] - 2026-10-07
6
+
7
+ ### Fixed
8
+
9
+ - Fixed the native ask dialog on TSP surfaces so it replaces the composer instead of opening as a modal over the transcript, keeping the transcript readable and scrollable.
10
+
11
+ ## [18.8.0] - 2026-10-07
12
+
13
+ ### Added
14
+
15
+ - SVG and Mermaid files now render as images or diagrams beneath their file cards, with SVG previews updating while the file streams and Mermaid previews appearing when the file is complete.
16
+ - Native tool cards can open expanded regardless of the transcript’s expansion state; the todo checklist uses this behavior.
17
+
18
+ ### Changed
19
+
20
+ - TSP composer placeholders now appear as the composer title in italicized curly quotes, with “What are we cooking?” used when no title is provided.
21
+ - TSP image transfers are more efficient across reconnects and multiple surfaces: images are sent once per connection, existing terminal blobs are detected before upload, and images in the Tern blob cache can be reused without crossing the terminal pty.
22
+ - Improved performance and responsiveness across the TUI, including large TSP messages and Markdown documents, streaming output, tool-result cards, debug logs, raw SSE and Git diff views, path and model searches, session and settings lists, plan review, agent transcripts, status updates, assistant links, Kitty images, and large prompt or evaluation content.
23
+ - Large debug logs and plan-review histories now retain bounded history to keep the interface responsive, while preserving the newest log entries.
24
+
25
+ ### Fixed
26
+
27
+ - Fixed slow Markdown processing for certain LaTeX environments and incomplete delimiters.
28
+ - Fixed excessive slowdown when formatting long semicolon-free JavaScript evaluations.
29
+ - Fixed prompt-editor lag in large drafts containing magic keywords.
30
+
5
31
  ## [18.7.0] - 2026-10-06
6
32
 
7
33
  ### Added
@@ -2972,55 +2998,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
2972
2998
 
2973
2999
  - **Experimental:** Overlay compositing for `ctx.ui.custom()` with `{ overlay: true }` option ([#558](https://github.com/badlogic/pi-mono/pull/558) by [@nicobailon](https://github.com/nicobailon))
2974
3000
 
2975
- ## [0.38.0] - 2026-01-08
2976
-
2977
- ### Added
2978
-
2979
- - `EditorComponent` interface for custom editor implementations
2980
- - `StdinBuffer` class to split batched stdin into individual sequences (adapted from [OpenTUI](https://github.com/anomalyco/opentui), MIT license)
2981
-
2982
- ### Fixed
2983
-
2984
- - Key presses no longer dropped when batched with other events over SSH ([#538](https://github.com/badlogic/pi-mono/pull/538))
2985
-
2986
- ## [0.37.8] - 2026-01-07
2987
-
2988
- ### Added
2989
-
2990
- - `Component.wantsKeyRelease` property to opt-in to key release events (default false)
2991
-
2992
- ### Fixed
2993
-
2994
- - TUI now filters out key release events by default, preventing double-processing of keys in editors and other components
2995
-
2996
- ## [0.37.7] - 2026-01-07
2997
-
2998
- ### Fixed
2999
-
3000
- - `matchesKey()` now correctly matches Kitty protocol sequences for unmodified letter keys (needed for key release events)
3001
-
3002
- ## [0.37.6] - 2026-01-06
3003
-
3004
- ### Added
3005
-
3006
- - Kitty keyboard protocol flag 2 support for key release events. New exports: `isKeyRelease(data)`, `isKeyRepeat(data)`, `KeyEventType` type. Terminals supporting Kitty protocol (Kitty, Ghostty, WezTerm) now send proper key-up events.
3007
-
3008
- ## [0.37.0] - 2026-01-05
3009
-
3010
- ### Fixed
3011
-
3012
- - Crash when pasting text with trailing whitespace exceeding terminal width through Markdown rendering ([#457](https://github.com/badlogic/pi-mono/pull/457) by [@robinwander](https://github.com/robinwander))
3013
-
3014
- ## [0.34.1] - 2026-01-04
3015
-
3016
- ### Added
3017
-
3018
- - Symbol key support in keybinding system: `SymbolKey` type with 32 symbol keys, `Key` constants (e.g., `Key.backtick`, `Key.comma`), updated `matchesKey()` and `parseKey()` to handle symbol input ([#450](https://github.com/badlogic/pi-mono/pull/450) by [@kaofelix](https://github.com/kaofelix))
3019
-
3020
- ## [0.34.0] - 2026-01-04
3021
-
3022
- ### Added
3023
-
3024
- - `Editor.getExpandedText()` method that returns text with paste markers expanded to their actual content ([#444](https://github.com/badlogic/pi-mono/pull/444) by [@aliou](https://github.com/aliou))
3025
-
3026
- Older entries are archived in [packages/tui/CHANGELOG.md@58141d4e5fa8](https://github.com/can1357/oh-my-pi/blob/58141d4e5fa892166024e2168866c45e0baacde3/packages/tui/CHANGELOG.md).
3001
+ Older entries are archived in [packages/tui/CHANGELOG.md@fa14205f838f](https://github.com/can1357/oh-my-pi/blob/fa14205f838f282fcea048c64fca74026789a492/packages/tui/CHANGELOG.md).
@@ -15,6 +15,8 @@ type DebugLogViewerModelOptions = {
15
15
  processPid?: number;
16
16
  hasOlderLogs?: () => boolean;
17
17
  loadOlderLogs?: (limitDays?: number) => Promise<string>;
18
+ /** Most entries kept; older history beyond it is not loaded. Defaults to {@link MAX_LOG_ENTRIES}. */
19
+ maxLogEntries?: number;
18
20
  };
19
21
  type ViewerRow = {
20
22
  kind: "warning";
@@ -33,6 +35,8 @@ export declare class DebugLogViewerModel {
33
35
  #private;
34
36
  constructor(logText: string, options?: DebugLogViewerModelOptions);
35
37
  get logCount(): number;
38
+ /** True once the entry cap is reached, so older history no longer loads. */
39
+ get historyLimitReached(): boolean;
36
40
  get visibleLogCount(): number;
37
41
  get rows(): readonly ViewerRow[];
38
42
  get cursorRowIndex(): number | undefined;
@@ -41,6 +41,8 @@ export declare function rawSseRecordLines(record: RawSseDebugRecord): string[];
41
41
  /** Bounded, observable capture of raw provider responses and SSE frames. */
42
42
  export declare class RawSseDebugBuffer {
43
43
  #private;
44
+ /** Bumped on every change to the captured window (append, eviction, clear); equal revisions mean equal snapshots. */
45
+ get revision(): number;
44
46
  subscribe(listener: () => void): () => void;
45
47
  recordResponse(response: ProviderResponseMetadata, model?: Model): void;
46
48
  recordEvent(event: RawSseEvent, model?: Model): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A tool call's {@link ToolFigure} drawn under its card, the way assistant
3
+ * Markdown draws that fence: ```svg as an inline image ({@link SvgFigure}),
4
+ * redrawn as the body streams; ```mermaid as ASCII art once the body is
5
+ * final. The card already shows the source, so unlike the assistant's fence
6
+ * it never falls back to code: {@link FenceFigure.draws} turns down fences
7
+ * this terminal shows as code, and an svg that does not rasterize stays blank.
8
+ */
9
+ import type { ImageBudget } from "../components/image.js";
10
+ import type { ToolFigure } from "../tools/renderer.js";
11
+ import type { Component } from "../tui.js";
12
+ /** Fence languages a {@link FenceFigure} draws. */
13
+ export type FenceFigureLang = "svg" | "mermaid";
14
+ export interface FenceFigureOptions {
15
+ /** Shared inline-image budget for an svg's rasters. */
16
+ budget?: ImageBudget;
17
+ /** The figure's rows changed outside a render: a raster landed. */
18
+ onChange: () => void;
19
+ }
20
+ export declare class FenceFigure implements Component {
21
+ #private;
22
+ /**
23
+ * Whether this terminal draws `figure` now: an svg where inline images show
24
+ * (`showImages` and a graphics protocol), a closed mermaid fence when
25
+ * diagram rendering is on and the source renders.
26
+ */
27
+ static draws(figure: ToolFigure, options: {
28
+ showImages: boolean;
29
+ }): figure is ToolFigure & {
30
+ readonly lang: FenceFigureLang;
31
+ };
32
+ readonly lang: FenceFigureLang;
33
+ constructor(lang: FenceFigureLang, options: FenceFigureOptions);
34
+ /** Feed the fence as it stands; its `lang` is this figure's. */
35
+ update(figure: ToolFigure): void;
36
+ /** Whether a closed svg's raster is still on its way. */
37
+ get pending(): boolean;
38
+ /** The drawing; no rows while an svg draws, or when it does not. */
39
+ render(width: number): readonly string[];
40
+ invalidate(): void;
41
+ dispose(): void;
42
+ }
@@ -1,5 +1,5 @@
1
1
  import { type ImageBudget } from "../components/image.js";
2
- import type { Markdown } from "../components/markdown.js";
2
+ import { type Markdown } from "../components/markdown.js";
3
3
  import type { Component } from "../tui.js";
4
4
  /** The active theme's color per figure token, for resolving a figure with `prepareSvg`. */
5
5
  export declare function svgFigurePalette(): Record<string, string>;
@@ -90,6 +90,8 @@ export declare class ToolExecutionComponent extends Container {
90
90
  * component-scoped so the TUI reuses every other root subtree (issue #4377).
91
91
  */
92
92
  tickSpinner(frame: number): void;
93
+ /** Whether the result's figure still waits for its raster; the transcript holds retirement meanwhile. */
94
+ isTranscriptBlockPending(): boolean;
93
95
  /**
94
96
  * Whether this block is ready to retire as immutable history. Partial
95
97
  * results, including detached background tasks, remain active and mutable
@@ -1,5 +1,5 @@
1
1
  import { type AutocompleteProvider } from "../autocomplete.js";
2
- import type { TspEditorDecoration, TspTone } from "@oh-my-pi/pi-wire";
2
+ import type { TspEditorDecoration, TspText, TspTone } from "@oh-my-pi/pi-wire";
3
3
  import { type DescribeContext, type NativeChild, type NativeNode, type NativeTextEdit, type NativeUiEvent } from "../native/node.js";
4
4
  import type { SymbolTheme } from "../symbols.js";
5
5
  import { type Component, type Focusable } from "../tui.js";
@@ -106,8 +106,11 @@ export declare class Editor implements Component, Focusable {
106
106
  * dialog's text field; only the prompt composer claims `omp.editor`).
107
107
  */
108
108
  describeLayout: ((input: NativeNode, cx: DescribeContext) => NativeEditorLayout) | undefined;
109
- /** TSP placeholder for the empty buffer; natively it replaces the rotating ANSI {@link placeholder} hints. */
110
- describePlaceholder: (() => string) | undefined;
109
+ /**
110
+ * TSP placeholder for the empty buffer; natively it replaces the rotating ANSI {@link placeholder} hints.
111
+ * Return a stable value (the same spans array) while it is unchanged: the editor node is reused by identity.
112
+ */
113
+ describePlaceholder: (() => TspText) | undefined;
111
114
  /** Called with the selected text when Visual mode yanks, so hosts can reach the system
112
115
  * clipboard — `packages/tui` deliberately has no clipboard dependency of its own. */
113
116
  onYank?: (text: string) => void;
@@ -250,6 +253,8 @@ export declare class Editor implements Component, Focusable {
250
253
  get atoms(): ReadonlyMap<string, string>;
251
254
  /** Monotonic buffer-content revision for render caches. Cursor-only movement does not advance it. */
252
255
  get textRevision(): number;
256
+ /** Monotonic atom-table revision for render caches: advances whenever {@link atoms} is mutated or replaced. */
257
+ get atomsRevision(): number;
253
258
  /** Whether the buffer text equals `value`, without `getText()`'s full join —
254
259
  * O(1) for the hot per-keystroke probes against short single-line values. */
255
260
  textEquals(value: string): boolean;
@@ -4,16 +4,21 @@ import type { SymbolTheme } from "../symbols.js";
4
4
  import type { Component } from "../tui.js";
5
5
  /** @internal exported for tests — the grown-line block-kind gate. */
6
6
  export declare function fastLineStartHazard(grownLine: string): boolean;
7
- /** @internal exported for tests — the windowed lexer's first-probe window size. */
8
- export declare const LEX_WINDOW_BYTES: number;
9
7
  /**
10
8
  * Lex a whole document with the renderer's rules (math, custom rules,
11
- * strikethrough), windowing anything large enough for the quadratic scan to
12
- * bite. `links` holds every reference definition, at any nesting depth. Callers
13
- * that split a document along its blocks (table charts) lex through this so
14
- * they see the blocks Markdown renders.
9
+ * strikethrough). Callers that split a document along its blocks (table
10
+ * charts) lex through this so they see the blocks Markdown renders.
15
11
  */
16
12
  export declare function lexDocument(text: string): TokensList;
13
+ /**
14
+ * `source` as a fenced code block in `lang`: the fence is one backtick longer
15
+ * than any backtick run in the source (at least three), so the block always
16
+ * closes where intended. `open` leaves it unclosed, the source verbatim, for
17
+ * a body that is still arriving.
18
+ */
19
+ export declare function fencedCode(lang: string, source: string, options?: {
20
+ open?: boolean;
21
+ }): string;
17
22
  /** A hyperlink as the renderer sees it: inline `[text](href)`, `<autolink>`, bare GFM URL, or reference link. */
18
23
  export interface MarkdownLink {
19
24
  /** Flattened visible label with whitespace collapsed to one row; falls back to `href` when empty. */
@@ -16,6 +16,20 @@ export interface FuzzyFilterResult<T> {
16
16
  score: number;
17
17
  }
18
18
  export declare function fuzzyMatch(query: string, text: string): FuzzyMatch;
19
+ /**
20
+ * A query normalized and tokenized once for matching against many texts.
21
+ *
22
+ * `fuzzyMatch(query, text)` re-normalizes `query` (and rebuilds its
23
+ * alphanumeric-swap variants) on every call; filters that test one query
24
+ * against every row should build one `FuzzyQuery` per keystroke instead.
25
+ * `query.match(text)` returns exactly what `fuzzyMatch(query, text)` returns.
26
+ */
27
+ export declare class FuzzyQuery {
28
+ #private;
29
+ constructor(query: string);
30
+ /** Same result as `fuzzyMatch(query, text)`; `text` may be a prepared {@link FuzzyText}. */
31
+ match(text: string | FuzzyText): FuzzyMatch;
32
+ }
19
33
  /**
20
34
  * A text prepared once for repeated fuzzy matching.
21
35
  *
@@ -30,13 +44,13 @@ export declare class FuzzyText {
30
44
  #private;
31
45
  constructor(text: string);
32
46
  /** Match `query` (space-separated tokens; all must match) against the prepared text. */
33
- match(query: string): FuzzyMatch;
47
+ match(query: string | FuzzyQuery): FuzzyMatch;
34
48
  }
35
49
  /**
36
50
  * Items prepared once for repeated {@link fuzzyRank}-equivalent ranking.
37
51
  *
38
- * `fuzzyRank` resolves each candidate's search index through the module cache,
39
- * which stops admitting entries at {@link INDEX_CACHE_MAX}; larger candidate
52
+ * `fuzzyRank` resolves each candidate's search index through the bounded module
53
+ * cache, which holds at most {@link INDEX_CACHE_MAX} entries; larger candidate
40
54
  * lists (the full model catalog) rebuild the overflow on every query. Hold one
41
55
  * corpus per stable candidate list and call {@link rank} per keystroke instead.
42
56
  */
@@ -49,7 +49,8 @@ export interface NativeBackendOptions {
49
49
  * The reply a v1 terminal is assumed to give before its real `hello` arrives
50
50
  * (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary, the
51
51
  * default APC limit and credits, the terminal's width, the appearance omp
52
- * already detected, and full motion.
52
+ * already detected, full motion, and `blobs`, so a resumed session's images
53
+ * are asked about rather than resent before the reply comes.
53
54
  */
54
55
  export declare function assumedTspHello(terminal: Terminal): TspHello;
55
56
  export declare class NativeBackend {
@@ -2,8 +2,10 @@
2
2
  * Content-addressed binary blobs (images) for `image` nodes.
3
3
  *
4
4
  * `describe()` registers the bytes and puts the returned id in `image.p.blob`;
5
- * the native backend uploads each referenced blob once per surface with verb
6
- * `b` before the frame that first references it.
5
+ * the native backend delivers each referenced blob once per terminal
6
+ * connection (asking the terminal first which it holds, or through its blob
7
+ * cache), else uploads it with verb `b` before the frame that first
8
+ * references it.
7
9
  */
8
10
  import type { TspProps } from "@oh-my-pi/pi-wire";
9
11
  import type { NativeNode } from "./node.js";
@@ -15,11 +15,10 @@ export type TspIncoming = {
15
15
  };
16
16
  /** Message parameters (`k=v`, ASCII, never containing `;`). */
17
17
  export type TspParams = Readonly<Record<string, string | number>>;
18
- /** Split `body` into pieces of at most `limit` UTF-8 bytes, never inside a code point. */
19
- export declare function splitUtf8(body: string, limit: number): string[];
20
18
  /**
21
- * Encode one logical message, chunked when `body` exceeds `limit` UTF-8 bytes.
22
- * Returns the complete byte string to write (all chunks, in order).
19
+ * Encode one logical message, chunked when `body` exceeds `limit` UTF-8 bytes:
20
+ * the first chunk carries `params`, every chunk `c=<id>` and all but the last
21
+ * `m=1`. Returns the complete byte string to write (all chunks, in order).
23
22
  */
24
23
  export declare function encodeTspMessage(verb: TspVerb, body: string, params?: TspParams, limit?: number): string;
25
24
  /** Encode a JSON message body. */
@@ -31,7 +31,14 @@ export type AgentTranscriptEntry = SessionMessageEntryLike | {
31
31
  };
32
32
  /** Local filesystem and session parsing capabilities supplied by the host. */
33
33
  export interface AgentTranscriptSource {
34
- fs: Pick<typeof fs, "openSync" | "closeSync" | "readSync" | "readFileSync" | "statSync">;
34
+ /**
35
+ * Sync primitives drive the first paint and the incremental tail. When the
36
+ * host also supplies `promises.readFile` (e.g. passes `node:fs` itself), full
37
+ * reloads after rotation/rewrite read the file off the UI thread.
38
+ */
39
+ fs: Pick<typeof fs, "openSync" | "closeSync" | "readSync" | "readFileSync" | "statSync"> & {
40
+ promises?: Pick<typeof fs.promises, "readFile">;
41
+ };
35
42
  parseEntries(text: string): AgentTranscriptEntry[];
36
43
  }
37
44
  export interface AgentTranscriptViewerDeps {
@@ -101,8 +101,11 @@ export declare class AskDialogComponent implements Component {
101
101
  handleInput(keyData: string): void;
102
102
  render(width: number): readonly string[];
103
103
  /**
104
- * A bottom-anchored glass sheet over the composer (the `overlay` hoists into
105
- * the terminal's layer; the dialog's own slot in the dock stays empty).
104
+ * In the dock, in the composer's place and framed as the composer is: a
105
+ * `col` with the prompt editor's root role (`omp.editor`), so the terminal
106
+ * gives it the composer's insets and spacing. Not a modal `overlay` sheet:
107
+ * one anchored at the bottom covered the transcript rows that explain the
108
+ * question and blocked scrolling until the question was answered.
106
109
  */
107
110
  describe(cx: DescribeContext): NativeNode;
108
111
  /**
@@ -15,6 +15,13 @@ import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.
15
15
  export declare class HistorySearchComponent extends OverlayPanel {
16
16
  #private;
17
17
  constructor(historyStorage: HistorySource, onSelect: (prompt: string) => void, onCancel: () => void);
18
+ /**
19
+ * Repaint hook for debounced result updates. Once set, query edits search
20
+ * storage after a {@link SEARCH_DEBOUNCE_MS} quiet period instead of on
21
+ * every key; without it every edit searches synchronously.
22
+ */
23
+ setOnRequestRender(callback: () => void): void;
24
+ dispose(): void;
18
25
  handleInput(keyData: string): void;
19
26
  /**
20
27
  * With the `picker` kind: a `size:"md"` sheet of past prompts (first line
@@ -29,6 +36,9 @@ export declare class HistorySearchComponent extends OverlayPanel {
29
36
  * Picker: a row click highlights, a second click or `Insert` inserts it
30
37
  * (Enter), `Close` cancels (Esc), `Clear search` empties the query.
31
38
  * List: picking a result does what highlighting it and pressing Enter does.
39
+ * A row picked while a debounced search is pending belongs to the previous
40
+ * query: results are refreshed first and the pick only lands if that entry
41
+ * is still among them.
32
42
  */
33
43
  handleNativeEvent(event: NativeUiEvent): void;
34
44
  }
@@ -262,6 +262,12 @@ export declare class ModelBrowser implements Component {
262
262
  setQuery(query: string): void;
263
263
  getSelected(): ModelBrowserItem | undefined;
264
264
  get visibleCount(): number;
265
+ /**
266
+ * Base items matching the live query, best first, without the separator
267
+ * row; undefined while the query is blank. Reuses the ranking the list
268
+ * already ran, so hosts can derive match counts without ranking again.
269
+ */
270
+ get queryMatches(): readonly ModelBrowserItem[] | undefined;
265
271
  /** Move selection to `selector`; false when it is not in the current view. */
266
272
  selectSelector(selector: string): boolean;
267
273
  /** True when `item`'s context window is smaller than the live session token count (grayed row; hosts compact before switching). */
@@ -7,7 +7,7 @@ import type { AppKeybinding } from "../app-keybindings.js";
7
7
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
8
8
  import type { ComposerFactsSource } from "../status-line/types.js";
9
9
  import { type MacOSSpellingFeatures } from "./macos-spelling.js";
10
- import type { TspEditorDecoration } from "@oh-my-pi/pi-wire";
10
+ import type { TspEditorDecoration, TspText } from "@oh-my-pi/pi-wire";
11
11
  import { type WordCompletionMethod } from "./word-completion.js";
12
12
  /** Live composer state the TSP layout shows; the interactive host wires {@link CustomEditor.composerState}. */
13
13
  export interface ComposerNativeState {
@@ -26,6 +26,8 @@ export interface ComposerNativeState {
26
26
  readonly running: boolean;
27
27
  /** The viewed subagent's lineage, outermost first and the viewed agent last; undefined on the main session. */
28
28
  readonly viewing?: readonly string[];
29
+ /** The session's title, quoted in italics as the TSP placeholder; undefined until it has one. */
30
+ readonly title?: string;
29
31
  }
30
32
  /** Independently switchable prose-assistance features of the composer. */
31
33
  export interface SpellingFeatures extends MacOSSpellingFeatures {
@@ -191,7 +193,8 @@ export declare class CustomEditor extends Editor {
191
193
  static readonly SHIMMER_PERIOD_MS = 1800;
192
194
  /** Decorate magic keywords, attachments, and the queue-composer header/list markers.
193
195
  * Queue shorthand reserves its first logical line as a dim `Queueing` label; sequential
194
- * item markers use the accent color so separate follow-ups remain visible while composing. */
196
+ * item markers use the accent color so separate follow-ups remain visible while composing.
197
+ * Called once per layout segment, so whole-buffer inputs are computed once per text revision. */
195
198
  decorateText: (text: string, context: EditorTextDecorationContext) => string;
196
199
  /**
197
200
  * TSP decorations: magic keywords shimmer (terminal-clocked) in the accent
@@ -219,8 +222,8 @@ export declare class CustomEditor extends Editor {
219
222
  setShimmerRepaintHandler(handler: (() => void) | undefined): void;
220
223
  /** Editing is available during bootstrap; atomic sends wait until submission is wired and enabled. */
221
224
  protected get nativeSendable(): boolean;
222
- /** Viewing a subagent, the draft goes to it: the placeholder names it. */
223
- describePlaceholder: () => string;
225
+ /** Viewing a subagent, the draft goes to it: the placeholder names it; else the session's title, quoted in italics. */
226
+ describePlaceholder: () => TspText;
224
227
  /** A shell-mode draft highlights as its language. */
225
228
  describeLanguage: () => string | undefined;
226
229
  /** Host-owned live state for the TSP composer (shell mode, effort chip, send/stop). */
@@ -25,7 +25,8 @@ export interface AssistantMessageHost {
25
25
  requestRender(): void;
26
26
  readonly imageBudget: ImageBudget;
27
27
  };
28
- resolveAssistantMessageLinks(texts: readonly string[]): Promise<ReadonlyMap<string, string>>;
28
+ /** Resolve already-extracted Markdown link destinations to local targets. */
29
+ resolveAssistantMessageLinkHrefs(hrefs: readonly string[]): Promise<ReadonlyMap<string, string>>;
29
30
  }
30
31
  /**
31
32
  * Resolve and cache the current session's model-authored prose links. Existing
@@ -39,6 +39,18 @@ export interface ToolRenderResult<TDetails = unknown> {
39
39
  details?: TDetails;
40
40
  isError?: boolean;
41
41
  }
42
+ /**
43
+ * A fence a tool call draws under its card, as assistant Markdown draws that
44
+ * fence. Any fence language may be named (`svg`, `mermaid`, `obj`); the
45
+ * transcript draws only those it draws as figures in assistant text.
46
+ */
47
+ export interface ToolFigure {
48
+ readonly lang: string;
49
+ /** The body so far. */
50
+ readonly source: string;
51
+ /** No more of the body arrives. */
52
+ readonly closed: boolean;
53
+ }
42
54
  /**
43
55
  * Per-renderer opt-in for a full viewport replay when the first result
44
56
  * replaces a painted pending-call render. A predicate receives the painted
@@ -103,6 +115,8 @@ export interface NativeToolView {
103
115
  readonly preview?: TspPreview | {
104
116
  readonly tail: number;
105
117
  } | "none";
118
+ /** Starts expanded whatever the transcript's expand state (the todo checklist); the user can still fold it. */
119
+ readonly open?: boolean;
106
120
  /** Render frameless (`frame:"inline"`): a head line plus a disclosed body. */
107
121
  readonly inline?: boolean;
108
122
  /** Head actions offered on hover (`copy`, `retry`). */
@@ -125,6 +139,14 @@ export interface ToolRenderer<TArgs = unknown, TDetails = unknown> {
125
139
  /** Semantic result view for TSP terminals; merged with the call view when {@link mergeCallAndResult}. */
126
140
  describeResult?(result: ToolRenderResult<TDetails>, options: RenderResultContextOptions, args?: TArgs): NativeToolView | undefined;
127
141
  mergeCallAndResult?: boolean;
142
+ /**
143
+ * The fence a call draws under its card (a `.svg` being written, as its
144
+ * image): growing while the args stream, closed once they are final; none
145
+ * after an error. `result` is undefined until the call has one. Rendered
146
+ * TUI only, and only fences the terminal draws as figures; native views put
147
+ * their drawing in their describe hooks.
148
+ */
149
+ figure?(args: TArgs, result: ToolRenderResult<TDetails> | undefined, options: RenderResultOptions): ToolFigure | undefined;
128
150
  /** Describes current activity without coupling a renderer to terminal layout. */
129
151
  activitySummary?(args: TArgs, context: ToolActivityContext): ToolActivitySummary;
130
152
  /** Render without background box, inline in the response flow */
@@ -236,7 +236,8 @@ export declare class OutputSink {
236
236
  constructor(options?: OutputSinkOptions);
237
237
  /**
238
238
  * Push a chunk of output. The buffer management and onChunk callback run
239
- * synchronously. File sink writes are deferred and serialized internally.
239
+ * synchronously; onChunk fires after the chunk is stored, so {@link preview}
240
+ * already includes it. File sink writes are deferred and serialized internally.
240
241
  *
241
242
  * `inline` substitutes a bounded representation for the in-memory buffer and
242
243
  * live preview while the complete chunk is mirrored to the artifact.
@@ -260,6 +261,15 @@ export declare class OutputSink {
260
261
  * branch in `dump()` against stale totals.
261
262
  */
262
263
  replace(text: string): void;
264
+ /**
265
+ * Current inline view without finalizing: the body {@link dump} would
266
+ * return right now (head + elision marker + rolling tail), minus any notice
267
+ * and minus the newline `dump()` appends after output that ends in a bare
268
+ * carriage return. Each call trims the rolling tail to its budget (a copy
269
+ * of the tail window once it overflowed), so call it from a throttled
270
+ * onChunk (`chunkThrottleMs`), not once per raw chunk.
271
+ */
272
+ preview(): string;
263
273
  dump(notice?: string): Promise<OutputSummary>;
264
274
  /**
265
275
  * Release the artifact spill descriptor on an exit path that skips
@@ -6,7 +6,7 @@ import { type ProcWriteDetails } from "./proc-render.js";
6
6
  import { type CfgWriteDetails } from "./cfg-render.js";
7
7
  import type { FileDiagnosticsResult } from "./lsp.js";
8
8
  import type { OutputMeta } from "./output-meta.js";
9
- import type { NativeToolView, RenderResultOptions, ToolActivityContext, ToolActivitySummary } from "./renderer.js";
9
+ import type { NativeToolView, RenderResultOptions, ToolActivityContext, ToolActivitySummary, ToolFigure } from "./renderer.js";
10
10
  import { type XdevRenderDispatch, type XdevMountedRenderer } from "./xdev.js";
11
11
  /** Details returned by the write tool for transcript rendering. */
12
12
  export interface WriteToolDetails {
@@ -37,6 +37,12 @@ interface WriteResult {
37
37
  details?: WriteToolDetails;
38
38
  isError?: boolean;
39
39
  }
40
+ /**
41
+ * The fence a file write draws as while its content streams and after: its
42
+ * extension names a figure language and it has content, closed once the args
43
+ * are final. None for URL-card writes, or after an error.
44
+ */
45
+ declare function writeFigure(args: WriteRenderArgs | undefined, result: WriteResult | undefined, options: RenderResultOptions): ToolFigure | undefined;
40
46
  declare const writeStreamingPreviewStateKey: unique symbol;
41
47
  /**
42
48
  * Per-component state for incrementally rendering a streamed write.
@@ -84,6 +90,7 @@ export declare const writeToolRenderer: {
84
90
  describeResult(result: WriteResult, options: RenderResultOptions & {
85
91
  renderContext?: WriteRenderContext;
86
92
  }, args?: WriteRenderArgs): NativeToolView | undefined;
93
+ figure: typeof writeFigure;
87
94
  mergeCallAndResult: true;
88
95
  forceFirstResultViewportRepaint: (args: unknown, options: RenderResultOptions) => boolean;
89
96
  };
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.7.0",
4
+ "version": "18.8.1",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -41,14 +41,14 @@
41
41
  "gen:glyphs": "bun scripts/gen-glyphs.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@oh-my-pi/omptype": "18.7.0",
45
- "@oh-my-pi/pi-agent-core": "18.7.0",
46
- "@oh-my-pi/pi-ai": "18.7.0",
47
- "@oh-my-pi/pi-catalog": "18.7.0",
48
- "@oh-my-pi/pi-natives": "18.7.0",
49
- "@oh-my-pi/pi-utils": "18.7.0",
50
- "@oh-my-pi/pi-wire": "18.7.0",
51
- "@oh-my-pi/snapcompact": "18.7.0"
44
+ "@oh-my-pi/omptype": "18.8.1",
45
+ "@oh-my-pi/pi-agent-core": "18.8.1",
46
+ "@oh-my-pi/pi-ai": "18.8.1",
47
+ "@oh-my-pi/pi-catalog": "18.8.1",
48
+ "@oh-my-pi/pi-natives": "18.8.1",
49
+ "@oh-my-pi/pi-utils": "18.8.1",
50
+ "@oh-my-pi/pi-wire": "18.8.1",
51
+ "@oh-my-pi/snapcompact": "18.8.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"