@oh-my-pi/pi-tui 18.8.5 → 18.8.7

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 (76) hide show
  1. package/CHANGELOG.md +30 -16
  2. package/README.md +7 -5
  3. package/dist/types/chat/assistant-message.d.ts +1 -0
  4. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  5. package/dist/types/chat/user-message.d.ts +2 -0
  6. package/dist/types/components/box.d.ts +1 -0
  7. package/dist/types/components/disclosure.d.ts +1 -0
  8. package/dist/types/components/image.d.ts +1 -0
  9. package/dist/types/components/key-value-list.d.ts +1 -0
  10. package/dist/types/components/layout/row.d.ts +1 -0
  11. package/dist/types/components/layout/stack.d.ts +1 -0
  12. package/dist/types/components/markdown.d.ts +1 -0
  13. package/dist/types/components/scroll-view.d.ts +1 -0
  14. package/dist/types/components/section.d.ts +1 -0
  15. package/dist/types/components/table.d.ts +1 -0
  16. package/dist/types/components/text.d.ts +1 -0
  17. package/dist/types/components/truncated-text.d.ts +1 -0
  18. package/dist/types/native/blobs.d.ts +3 -1
  19. package/dist/types/overlays/session-selector.d.ts +6 -2
  20. package/dist/types/render/output-pane.d.ts +1 -0
  21. package/dist/types/render/render-utils.d.ts +1 -0
  22. package/dist/types/render/tool-card.d.ts +6 -0
  23. package/dist/types/render/width-aware-text.d.ts +2 -0
  24. package/dist/types/terminal.d.ts +10 -2
  25. package/dist/types/theme/theme.d.ts +1 -1
  26. package/dist/types/theme/tui-adapters.d.ts +3 -0
  27. package/dist/types/tools/write.d.ts +1 -1
  28. package/dist/types/tools/yield.d.ts +22 -0
  29. package/dist/types/tui.d.ts +11 -0
  30. package/package.json +13 -9
  31. package/src/chat/advisor-message.ts +11 -0
  32. package/src/chat/assistant-message.ts +8 -2
  33. package/src/chat/compaction-summary-message.ts +8 -0
  34. package/src/chat/read-tool-group.ts +6 -4
  35. package/src/chat/skill-message.ts +6 -0
  36. package/src/chat/table-chart.ts +17 -20
  37. package/src/chat/tool-execution.ts +18 -0
  38. package/src/chat/user-message.ts +14 -0
  39. package/src/chrome/transcript-container.ts +32 -1
  40. package/src/components/box.ts +5 -0
  41. package/src/components/disclosure.ts +8 -0
  42. package/src/components/image.ts +7 -5
  43. package/src/components/key-value-list.ts +4 -0
  44. package/src/components/layout/row.ts +7 -0
  45. package/src/components/layout/stack.ts +7 -0
  46. package/src/components/markdown.ts +190 -8
  47. package/src/components/scroll-view.ts +5 -0
  48. package/src/components/section.ts +5 -0
  49. package/src/components/table.ts +4 -0
  50. package/src/components/text.ts +7 -0
  51. package/src/components/truncated-text.ts +5 -0
  52. package/src/native/backend.ts +16 -15
  53. package/src/native/blobs.ts +43 -10
  54. package/src/native/reconcile.ts +4 -0
  55. package/src/overlays/model-hub.ts +39 -4
  56. package/src/overlays/session-selector.ts +40 -22
  57. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  58. package/src/prompt/attachment-chips.ts +13 -10
  59. package/src/prompt/tips.txt +2 -1
  60. package/src/prompt/welcome.ts +46 -9
  61. package/src/render/output-pane.ts +6 -0
  62. package/src/render/render-utils.ts +11 -0
  63. package/src/render/tool-card.ts +15 -0
  64. package/src/render/width-aware-text.ts +5 -0
  65. package/src/terminal.ts +24 -3
  66. package/src/theme/theme.ts +1 -0
  67. package/src/theme/tui-adapters.ts +68 -0
  68. package/src/tools/bash.ts +10 -11
  69. package/src/tools/edit.ts +142 -120
  70. package/src/tools/fetch.ts +5 -6
  71. package/src/tools/index.ts +2 -0
  72. package/src/tools/mcp.ts +15 -2
  73. package/src/tools/read.ts +7 -5
  74. package/src/tools/write.ts +70 -56
  75. package/src/tools/yield.ts +225 -0
  76. package/src/tui.ts +25 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.8.7] - 2026-10-09
6
+
7
+ ### Added
8
+
9
+ - Added cache-release hooks for TUI components and tool cards, allowing extensions to discard derived render data without rebuilding content ([#13632](https://github.com/can1357/oh-my-pi/pull/13632) by [@iliaal](https://github.com/iliaal)).
10
+
11
+ ### Changed
12
+
13
+ - Subagent "Submit Result" cards now show the submitted result (its fields as a tree, or the report text), the section it fills, and why a submission was rejected, instead of only "Result submitted.".
14
+ - Reduced memory held by retired transcript history in long sessions ([#13632](https://github.com/can1357/oh-my-pi/pull/13632) by [@iliaal](https://github.com/iliaal)).
15
+
16
+ ### Fixed
17
+
18
+ - Reduced memory retained by discarded TSP images and previews ([#14336](https://github.com/can1357/oh-my-pi/pull/14336) by [@iliaal](https://github.com/iliaal)).
19
+ - Fixed startup capability probes printing as text in the prompt (e.g. `25a1;stsp;q;{…}pppppp`) on terminals that cannot parse them, such as macOS Terminal.app.
20
+ - Fixed the `/resume` picker flashing while a search runs over a large session history: background fuzzy matches now land in one update instead of reordering the list dozens of times per keystroke.
21
+
22
+ ## [18.8.6] - 2026-10-08
23
+
24
+ ### Changed
25
+
26
+ - Improved rendering performance for streaming long Markdown lists while preserving nested items, numbering, and reference links.
27
+
28
+ ### Fixed
29
+
30
+ - Added package exports for the native Tern/TSP modules, allowing extensions in compiled `omp` binaries to import them.
31
+ - Fixed the Tern `/model` picker's Roles tab so typing and Backspace consistently edit the search field without triggering role commands.
32
+ - Fixed the session picker so sessions retain and display their directory when switching between the current folder and all-project views.
33
+
5
34
  ## [18.8.5] - 2026-10-08
6
35
 
7
36
  ### Added
@@ -3011,19 +3040,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
3011
3040
  - Cursor now moves to end of content on exit, preventing status line from being overwritten ([#629](https://github.com/badlogic/pi-mono/pull/629) by [@tallshort](https://github.com/tallshort))
3012
3041
  - Reset ANSI styles after each rendered line to prevent style leakage
3013
3042
 
3014
- ## [0.42.5] - 2026-01-11
3015
-
3016
- ### Fixed
3017
-
3018
- - Reduced flicker by only re-rendering changed lines ([#617](https://github.com/badlogic/pi-mono/pull/617) by [@ogulcancelik](https://github.com/ogulcancelik))
3019
- - Cursor position tracking when content shrinks with unchanged remaining lines
3020
- - TUI renders with wrong dimensions after suspend/resume if terminal was resized while suspended ([#599](https://github.com/badlogic/pi-mono/issues/599))
3021
- - Pasted content containing Kitty key release patterns (e.g., `:3F` in MAC addresses) was incorrectly filtered out ([#623](https://github.com/badlogic/pi-mono/pull/623) by [@ogulcancelik](https://github.com/ogulcancelik))
3022
-
3023
- ## [0.39.0] - 2026-01-08
3024
-
3025
- ### Added
3026
-
3027
- - **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))
3028
-
3029
- Older entries are archived in [packages/tui/CHANGELOG.md@fa14205f838f](https://github.com/can1357/oh-my-pi/blob/fa14205f838f282fcea048c64fca74026789a492/packages/tui/CHANGELOG.md).
3043
+ Older entries are archived in [packages/tui/CHANGELOG.md@4787659281ba](https://github.com/can1357/oh-my-pi/blob/4787659281ba47f5a3707bbf5ee61742e9373c42/packages/tui/CHANGELOG.md).
package/README.md CHANGED
@@ -66,14 +66,16 @@ interface Component {
66
66
  render(width: number): readonly string[];
67
67
  handleInput?(data: string): void;
68
68
  invalidate?(): void;
69
+ releaseRenderCaches?(): void;
69
70
  }
70
71
  ```
71
72
 
72
- | Method | Description |
73
- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
74
- | `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. The result is component-owned and immutable to callers; return the same array reference when unchanged (enables renderer memoization) and a new array when content changed. |
75
- | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
76
- | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
73
+ | Method | Description |
74
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
75
+ | `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. The result is component-owned and immutable to callers; return the same array reference when unchanged (enables renderer memoization) and a new array when content changed. |
76
+ | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
77
+ | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
78
+ | `releaseRenderCaches?()` | Drops only derived render caches. The next `render()` must return the same rows without eager rebuilds, renderer callbacks, image conversions, or child replacement. |
77
79
 
78
80
  ## Built-in Components
79
81
 
@@ -54,6 +54,7 @@ export declare class AssistantMessageComponent extends Container {
54
54
  */
55
55
  setServedModelMismatch(info: ServedModelMismatch | undefined): void;
56
56
  invalidate(): void;
57
+ releaseRenderCaches(): void;
57
58
  setHideThinkingBlock(hide: boolean): void;
58
59
  /**
59
60
  * Allow or withhold retiring finished lines into native scrollback while the
@@ -31,6 +31,7 @@ declare class SummaryMessageComponent implements Component {
31
31
  handleNativeEvent(event: NativeUiEvent): void;
32
32
  setIgnoreTight(ignore: boolean): this;
33
33
  invalidate(): void;
34
+ releaseRenderCaches(): void;
34
35
  dispose(): void;
35
36
  render(width: number): readonly string[];
36
37
  }
@@ -33,6 +33,7 @@ export declare function userBubbleColor(options?: UserBubbleOptions, tokenRegex?
33
33
  export declare class UserMessageComponent extends Container implements ReactionTarget {
34
34
  #private;
35
35
  constructor(text: string, options?: UserBubbleOptions);
36
+ releaseRenderCaches(): void;
36
37
  setReaction(emoji: string): void;
37
38
  /**
38
39
  * A head-less user-toned frame with the prompt as markdown under its
@@ -75,6 +76,7 @@ export declare class CollapsedSyntheticMessageComponent implements Component {
75
76
  handleNativeEvent(event: NativeUiEvent): void;
76
77
  setIgnoreTight(ignore: boolean): this;
77
78
  invalidate(): void;
79
+ releaseRenderCaches(): void;
78
80
  dispose(): void;
79
81
  render(width: number): readonly string[];
80
82
  }
@@ -30,6 +30,7 @@ export declare class Box implements Component {
30
30
  setBgFn(bgFn?: (text: string) => string): void;
31
31
  setBorder(border?: BoxBorder): void;
32
32
  invalidate(): void;
33
+ releaseRenderCaches(): void;
33
34
  /**
34
35
  * A `card` when the box has a border or background: the background token
35
36
  * picks tone/role (user message, tool state, selection), a border colour
@@ -36,6 +36,7 @@ export declare class Disclosure implements Component {
36
36
  setExpanded(expanded: boolean): void;
37
37
  setIgnoreTight(ignore: boolean): this;
38
38
  invalidate(): void;
39
+ releaseRenderCaches(): void;
39
40
  dispose(): void;
40
41
  debugState(): Record<string, unknown>;
41
42
  get debugChildren(): readonly Component[];
@@ -227,6 +227,7 @@ export declare class Image implements Component {
227
227
  /** Return source metadata without exposing the encoded image buffer. */
228
228
  debugState(): Record<string, unknown>;
229
229
  invalidate(): void;
230
+ releaseRenderCaches(): void;
230
231
  /**
231
232
  * A native `image` backed by a content-addressed blob; the terminal fits
232
233
  * it. Cell caps become `ch`/`lines` bounds. The inline-image budget and
@@ -21,6 +21,7 @@ export declare class KeyValueList implements Component {
21
21
  constructor(rows: readonly KeyValueRow[], options: KeyValueListOptions);
22
22
  setRows(rows: readonly KeyValueRow[]): boolean;
23
23
  invalidate(): void;
24
+ releaseRenderCaches(): void;
24
25
  /** An aligned `kv` grid; label width and value wrapping are the terminal's. */
25
26
  describe(_cx: DescribeContext): NativeNode;
26
27
  render(width: number): readonly string[];
@@ -48,6 +48,7 @@ export declare class Row implements Component, MouseRoutable {
48
48
  setDecorations(prefix: LayoutDecoration | undefined, gap: LayoutDecoration | undefined, suffix: LayoutDecoration | undefined): void;
49
49
  setIgnoreTight(ignore: boolean): this;
50
50
  invalidate(): void;
51
+ releaseRenderCaches(): void;
51
52
  dispose(): void;
52
53
  /**
53
54
  * A flex `row`: each child sits in a slot carrying its width bounds
@@ -43,6 +43,7 @@ export declare class Stack implements Component, MouseRoutable {
43
43
  setHeight(height: number | undefined): void;
44
44
  setIgnoreTight(ignore: boolean): this;
45
45
  invalidate(): void;
46
+ releaseRenderCaches(): void;
46
47
  dispose(): void;
47
48
  /**
48
49
  * A `col` of slots carrying each child's height bounds (fixed → min = max
@@ -124,6 +124,7 @@ export declare class Markdown implements Component {
124
124
  debugState(): Record<string, unknown>;
125
125
  setText(text: string): boolean;
126
126
  invalidate(): void;
127
+ releaseRenderCaches(): void;
127
128
  /**
128
129
  * The unrendered source as an `md` node, `stream` while the streaming
129
130
  * (transient) cache is on. Append-only growth yields a fresh node of the
@@ -92,6 +92,7 @@ export declare class ScrollView implements Component {
92
92
  /** Apply Arrow, Shift+Arrow, Page, Home, and End navigation keys. */
93
93
  handleScrollKey(data: string): boolean;
94
94
  invalidate(): void;
95
+ releaseRenderCaches(): void;
95
96
  /** Permanently release a wrapped child's timers, subscriptions, and other resources. */
96
97
  dispose(): void;
97
98
  /**
@@ -15,6 +15,7 @@ export declare class Section implements Component {
15
15
  #private;
16
16
  constructor(options: SectionOptions);
17
17
  invalidate(): void;
18
+ releaseRenderCaches(): void;
18
19
  dispose(): void;
19
20
  /** A `section` headed by the styled title; the rule is the terminal's. */
20
21
  describe(_cx: DescribeContext): NativeNode;
@@ -29,6 +29,7 @@ export declare class Table implements Component {
29
29
  constructor(rows: readonly (readonly TableCell[])[], columns: readonly TableColumn[], options?: TableOptions);
30
30
  setRows(rows: readonly (readonly TableCell[])[]): boolean;
31
31
  invalidate(): void;
32
+ releaseRenderCaches(): void;
32
33
  /**
33
34
  * A native `table`. Column priorities carry over unchanged (lower gives
34
35
  * way first); truncating columns truncate at the end, the rest keep
@@ -26,6 +26,7 @@ export declare class Text implements Component {
26
26
  */
27
27
  setStyleFn(styleFn?: (text: string) => string): this;
28
28
  invalidate(): void;
29
+ releaseRenderCaches(): void;
29
30
  /**
30
31
  * Wrapped text; styling re-derived from the styled source so a theme swap
31
32
  * re-resolves tokens. A background fill becomes the node's tone.
@@ -9,6 +9,7 @@ export declare class TruncatedText implements Component {
9
9
  /** Return bounded source text and the last-known truncation state. */
10
10
  debugState(): Record<string, unknown>;
11
11
  invalidate(): void;
12
+ releaseRenderCaches(): void;
12
13
  /** First line only, clamped to one visual line with an end ellipsis. */
13
14
  describe(_cx: DescribeContext): NativeNode;
14
15
  render(width: number): readonly string[];
@@ -14,10 +14,12 @@ export interface NativeBlob {
14
14
  readonly mime: string;
15
15
  readonly bytes: Uint8Array;
16
16
  }
17
- /** Register `bytes` and return their content address (sha256 hex) for `image.p.blob`. */
17
+ /** Register bytes for process-lifetime, id-only use. Prefer {@link nativeImageNode} for owned images. */
18
18
  export declare function registerNativeBlob(bytes: Uint8Array, mime: string): string;
19
19
  /** A registered blob by id. */
20
20
  export declare function getNativeBlob(id: string): NativeBlob | undefined;
21
+ /** An image whose props own its bytes, including while hidden, unmounted, or awaiting upload. */
22
+ export declare function nativeImageNode(bytes: Uint8Array, mime: string, p?: Omit<TspProps<"image">, "blob" | "builtin">, key?: string): NativeNode;
21
23
  /**
22
24
  * An `image` node for a base64 payload: the bytes are registered as a blob and
23
25
  * the pixel size probed from the header. Decoding and hashing cost real time,
@@ -71,7 +71,7 @@ declare class SessionList<T extends SessionSelectorEntry> implements Component {
71
71
  onExit: () => void;
72
72
  onToggleScope?: () => void;
73
73
  onDeleteRequest?: (session: T) => void;
74
- /** Re-render hook for async list updates (fuzzy scan chunks, history merge). */
74
+ /** Re-render hook for async list updates (fuzzy scan completion, history merge). */
75
75
  onRequestRender?: () => void;
76
76
  constructor(sessions: T[], showCwd?: boolean, historyMatcher?: SessionHistoryMatcher, getTerminalRows?: () => number, pinnedIds?: ReadonlySet<string>, currentSessionPath?: string | (() => string | undefined));
77
77
  /** Replace the visible dataset, e.g. when toggling folder/all-projects scope. */
@@ -129,7 +129,11 @@ export interface SessionSelectorOptions<T extends SessionSelectorEntry = Session
129
129
  title?: string;
130
130
  /** Fixed scope label, or false to omit the scope suffix. */
131
131
  scopeLabel?: string | false;
132
- /** Show each session's working directory in the list. */
132
+ /**
133
+ * Show each session's working directory in the list. Defaults to on when
134
+ * the session files live in more than one directory (e.g. the folder scope
135
+ * merging a repository's worktrees).
136
+ */
133
137
  showCwd?: boolean;
134
138
  /**
135
139
  * Reads the live terminal height so the visible window fits the viewport.
@@ -92,6 +92,7 @@ export declare class OutputPane implements Component {
92
92
  describe(_cx: DescribeContext): NativeNode;
93
93
  render(width: number): readonly string[];
94
94
  invalidate(): void;
95
+ releaseRenderCaches(): void;
95
96
  }
96
97
  /** Default styling for plain tool output while preserving existing ANSI. */
97
98
  export declare function styleToolOutputLine(line: string, theme: Theme): string;
@@ -298,6 +298,7 @@ export interface RenderedStringCache {
298
298
  export declare function createRenderedStringCache(): RenderedStringCache;
299
299
  /** Drop the memo so the next lookup re-renders (e.g. the render function identity changed). */
300
300
  export declare function invalidateRenderedStringCache(cache: RenderedStringCache): void;
301
+ export declare function releaseRenderedStringCache(cache: RenderedStringCache): void;
301
302
  /** Reuse a rendered string while its theme, expansion, content, and salt match. */
302
303
  export declare function cachedRenderedString(cache: RenderedStringCache | undefined, theme: Theme, expanded: boolean, salt: string, content: string, render: () => string): string;
303
304
  /**
@@ -46,6 +46,11 @@ export interface ToolCardOptions {
46
46
  paddingY?: number;
47
47
  ignoreTight?: boolean;
48
48
  onInvalidate?: () => void;
49
+ /**
50
+ * Drop builder-owned memos the next build recomputes. Called by
51
+ * {@link ToolCard.releaseRenderCaches}, which never calls `onInvalidate`.
52
+ */
53
+ onReleaseRenderCaches?: () => void;
49
54
  onDispose?: () => void;
50
55
  }
51
56
  /** Map transcript lifecycle terminology onto the output frame's visual states. */
@@ -71,6 +76,7 @@ export declare class ToolCard implements Component {
71
76
  handleInput(data: string): void;
72
77
  setIgnoreTight(ignore: boolean): void;
73
78
  invalidate(): void;
79
+ releaseRenderCaches(): void;
74
80
  dispose(): void;
75
81
  }
76
82
  /** Construct a lazy framed tool card. */
@@ -20,6 +20,8 @@ export declare class WidthAwareText implements Component {
20
20
  setCustomBgFn(customBgFn?: (text: string) => string): void;
21
21
  setIgnoreTight(ignore: boolean): this;
22
22
  invalidate(): void;
23
+ /** The formatted text stays: the inner `Text` renders from it, so dropping it would free nothing. */
24
+ releaseRenderCaches(): void;
23
25
  /**
24
26
  * Re-run the formatter on the next render because its inputs changed.
25
27
  * Unlike {@link invalidate}, the inner `Text` keeps its wrap cache, so a
@@ -128,13 +128,21 @@ export type TspHelloHandler = (hello: TspHello | null) => void;
128
128
  export type CursorShape = "default" | "block" | "underline" | "bar";
129
129
  export declare const CURSOR_SHAPE_CODES: Record<CursorShape, number>;
130
130
  export interface Terminal {
131
+ /**
132
+ * Start the terminal with input, resize, and host-disconnect handlers.
133
+ * Unless input is deferred, this sends capability probes; a terminal that
134
+ * cannot parse one may leave concealed bytes on the cursor row, so the
135
+ * caller repaints after starting.
136
+ */
131
137
  start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
132
138
  /**
133
139
  * Take ownership of stdin after a `deferInput` start: enable raw mode,
134
140
  * attach input handlers, and run the capability probes start() skipped.
135
141
  * Bytes the user typed in cooked mode meanwhile are replayed through
136
- * `onInput`. No-op when input was never deferred. Optional so custom
137
- * Terminals built against older pi-tui versions keep working.
142
+ * `onInput`. No-op when input was never deferred. Like {@link start}, the
143
+ * probes may leave concealed bytes on the cursor row, so the caller
144
+ * repaints afterwards. Optional so custom Terminals built against older
145
+ * pi-tui versions keep working.
138
146
  */
139
147
  enableInput?(): void;
140
148
  stop(): void;
@@ -6,7 +6,7 @@ export { getAvailableThemes, getAvailableThemesWithPaths, getThemeByName, type T
6
6
  export { isValidThemeColor, type ThemeBg, type ThemeColor } from "./schema.js";
7
7
  export { getAvailableSymbolPresets, isValidSymbolPreset, type SpinnerType, type SymbolKey, type SymbolPreset, } from "./symbols.js";
8
8
  export { Theme } from "./theme-class.js";
9
- export { createHighlightStream, getEditorTheme, getMarkdownTheme, getSelectListTheme, getSettingsListTheme, getSymbolTheme, highlightCode, setMarkdownMermaidRendering, warmHighlighter, } from "./tui-adapters.js";
9
+ export { createHighlightStream, getEditorTheme, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getSelectListTheme, getSettingsListTheme, getSymbolTheme, highlightCode, setMarkdownMermaidRendering, warmHighlighter, } from "./tui-adapters.js";
10
10
  /** Active theme; imports remain live across initialization, previews, and theme switches. */
11
11
  export declare var theme: Theme;
12
12
  type ThemeBinding = (value: Theme) => void;
@@ -16,6 +16,9 @@ export declare function createHighlightStream(lang?: string, highlightTheme?: Th
16
16
  /** Warm native syntax grammars off-thread once per process. */
17
17
  export declare function warmHighlighter(): Promise<void>;
18
18
  export declare function getSymbolTheme(): SymbolTheme;
19
+ /** Only themes assembled here have stable callbacks and private link state. */
20
+ export declare function canCacheMarkdownListItems(candidate: MarkdownTheme): boolean;
21
+ export declare function getMarkdownThemeWithLinkTargets(targets: ReadonlyMap<string, string>): MarkdownTheme;
19
22
  export declare function setMarkdownMermaidRendering(enabled: boolean): void;
20
23
  export declare function getMarkdownTheme(): MarkdownTheme;
21
24
  export declare function getSelectListTheme(): SelectListTheme;
@@ -81,7 +81,7 @@ export declare const writeToolRenderer: {
81
81
  renderCall(args: WriteRenderArgs, options: RenderResultOptions & WriteStreamingPreviewStateCarrier & {
82
82
  renderContext?: WriteRenderContext;
83
83
  }, uiTheme: Theme): Component | undefined;
84
- renderResult(result: WriteResult, options: RenderResultOptions & {
84
+ renderResult(result: WriteResult, options: RenderResultOptions & WriteStreamingPreviewStateCarrier & {
85
85
  renderContext?: WriteRenderContext;
86
86
  }, uiTheme: Theme, args?: WriteRenderArgs): Component;
87
87
  describeCall(args: WriteRenderArgs, options: RenderResultOptions & {
@@ -0,0 +1,22 @@
1
+ import type { Theme } from "../theme/theme.js";
2
+ import type { Component } from "../tui.js";
3
+ import type { NativeToolView, RenderResultContextOptions, RenderResultOptions, ToolRenderResult } from "./renderer.js";
4
+ import type { YieldItem } from "./task.js";
5
+ /** `yield` arguments as sent; strict-mode providers send omitted optionals as `null`. */
6
+ interface YieldRenderArgs {
7
+ type?: string | string[] | null;
8
+ data?: unknown;
9
+ error?: string | null;
10
+ /** 1-based workpool item number. */
11
+ key?: number;
12
+ }
13
+ /** Shows a `yield` call's submitted data instead of its model-facing acknowledgement. */
14
+ export declare const yieldToolRenderer: {
15
+ renderCall(args: YieldRenderArgs, options: RenderResultOptions, theme: Theme): Component;
16
+ renderResult(result: ToolRenderResult<YieldItem>, options: RenderResultContextOptions, theme: Theme, args?: YieldRenderArgs): Component;
17
+ describeCall(args: YieldRenderArgs, options: RenderResultOptions): NativeToolView;
18
+ describeResult(result: ToolRenderResult<YieldItem>, _options: RenderResultContextOptions, args?: YieldRenderArgs): NativeToolView;
19
+ mergeCallAndResult: true;
20
+ animatedPendingPreview: true;
21
+ };
22
+ export {};
@@ -169,6 +169,16 @@ export interface Component {
169
169
  * Called when theme changes or when component needs to re-render from scratch.
170
170
  */
171
171
  invalidate?(): void;
172
+ /**
173
+ * Optional hook to drop memoized render output (rows, parse and wrap state)
174
+ * that the next `render()` can rebuild from state the component keeps.
175
+ * Unlike {@link invalidate}, it must not rebuild anything eagerly: no
176
+ * renderer or extension callbacks, no image conversions, and no child
177
+ * replacement or disposal. The next render must return the same rows it
178
+ * would have returned without the release. Components without it are
179
+ * simply not released.
180
+ */
181
+ releaseRenderCaches?(): void;
172
182
  /**
173
183
  * Optional hook to set whether this component ignores tight layout mode.
174
184
  */
@@ -308,6 +318,7 @@ export declare class Container implements Component {
308
318
  /** Dispose every child, then detach it from this container. */
309
319
  disposeChildren(): void;
310
320
  invalidate(): void;
321
+ releaseRenderCaches(): void;
311
322
  /**
312
323
  * Propagate teardown to children. Call when the container's children are
313
324
  * being permanently discarded (not when they are detached for reuse — use
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.8.5",
4
+ "version": "18.8.7",
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.8.5",
45
- "@oh-my-pi/pi-agent-core": "18.8.5",
46
- "@oh-my-pi/pi-ai": "18.8.5",
47
- "@oh-my-pi/pi-catalog": "18.8.5",
48
- "@oh-my-pi/pi-natives": "18.8.5",
49
- "@oh-my-pi/pi-utils": "18.8.5",
50
- "@oh-my-pi/pi-wire": "18.8.5",
51
- "@oh-my-pi/snapcompact": "18.8.5"
44
+ "@oh-my-pi/omptype": "18.8.7",
45
+ "@oh-my-pi/pi-agent-core": "18.8.7",
46
+ "@oh-my-pi/pi-ai": "18.8.7",
47
+ "@oh-my-pi/pi-catalog": "18.8.7",
48
+ "@oh-my-pi/pi-natives": "18.8.7",
49
+ "@oh-my-pi/pi-utils": "18.8.7",
50
+ "@oh-my-pi/pi-wire": "18.8.7",
51
+ "@oh-my-pi/snapcompact": "18.8.7"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -105,6 +105,10 @@
105
105
  "types": "./dist/types/components/*.d.ts",
106
106
  "import": "./src/components/*.ts"
107
107
  },
108
+ "./native/*": {
109
+ "types": "./dist/types/native/*.d.ts",
110
+ "import": "./src/native/*.ts"
111
+ },
108
112
  "./*.js": "./src/*.ts"
109
113
  }
110
114
  }
@@ -52,6 +52,10 @@ class AdvisorHeader implements Component {
52
52
  this.#uiTheme = uiTheme;
53
53
  }
54
54
 
55
+ releaseRenderCaches(): void {
56
+ this.#cache = undefined;
57
+ }
58
+
55
59
  invalidate(): void {
56
60
  this.#cache = undefined;
57
61
  }
@@ -82,6 +86,10 @@ class AdvisorNotes implements Component {
82
86
  this.#uiTheme = uiTheme;
83
87
  }
84
88
 
89
+ releaseRenderCaches(): void {
90
+ this.#cache = undefined;
91
+ }
92
+
85
93
  invalidate(): void {
86
94
  this.#cache = undefined;
87
95
  }
@@ -236,6 +244,9 @@ export function createAdvisorMessageCard(
236
244
  invalidate(): void {
237
245
  disclosure.invalidate();
238
246
  },
247
+ releaseRenderCaches(): void {
248
+ disclosure.releaseRenderCaches();
249
+ },
239
250
  dispose(): void {
240
251
  disclosure.dispose();
241
252
  },
@@ -9,7 +9,7 @@ import { formatDuration, formatNumber } from "@oh-my-pi/pi-utils";
9
9
  import chalk from "@oh-my-pi/pi-utils/chalk";
10
10
  import { LRUCache } from "@oh-my-pi/pi-utils/lru";
11
11
  import type { AssistantThinkingRenderer } from "./extension-types";
12
- import { ensureThemeSync, getMarkdownTheme, getThemeEpoch, theme } from "../theme";
12
+ import { ensureThemeSync, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getThemeEpoch, theme } from "../theme";
13
13
  import { card, col, elapsed, node, span, text } from "../native/describe";
14
14
  import { hasTranscriptActions, runTranscriptAction } from "./transcript-actions";
15
15
  import type { NativeChild, NativeNode, NativeUiEvent } from "../native/node";
@@ -394,7 +394,7 @@ export class AssistantMessageComponent extends Container {
394
394
  if (this.#markdownTheme) return this.#markdownTheme;
395
395
  const base = getMarkdownTheme();
396
396
  const snapshot = this.#linkTargets;
397
- const markdownTheme = snapshot.size > 0 ? { ...base, resolveLink: (href: string) => snapshot.get(href) } : base;
397
+ const markdownTheme = snapshot.size > 0 ? getMarkdownThemeWithLinkTargets(snapshot) : base;
398
398
  this.#markdownTheme = markdownTheme;
399
399
  return markdownTheme;
400
400
  }
@@ -575,6 +575,12 @@ export class AssistantMessageComponent extends Container {
575
575
  }
576
576
  }
577
577
 
578
+ override releaseRenderCaches(): void {
579
+ super.releaseRenderCaches();
580
+ this.#dropStableRenders();
581
+ this.#emergencyText?.releaseRenderCaches();
582
+ }
583
+
578
584
  setHideThinkingBlock(hide: boolean): void {
579
585
  this.#hideThinkingBlock = hide;
580
586
  this.#thinkingCollapsed.clear();
@@ -53,6 +53,10 @@ class DividerSummary implements Component {
53
53
  this.#cache = undefined;
54
54
  }
55
55
 
56
+ releaseRenderCaches(): void {
57
+ this.#cache = undefined;
58
+ }
59
+
56
60
  render(width: number): readonly string[] {
57
61
  width = Math.max(1, width);
58
62
  if (this.#cache?.width === width) return this.#cache.lines;
@@ -150,6 +154,10 @@ class SummaryMessageComponent implements Component {
150
154
  if (this.#ignoreTight !== undefined) this.#disclosure.setIgnoreTight(this.#ignoreTight);
151
155
  }
152
156
 
157
+ releaseRenderCaches(): void {
158
+ this.#disclosure.releaseRenderCaches();
159
+ }
160
+
153
161
  dispose(): void {
154
162
  if (this.#disposed) return;
155
163
  this.#disposed = true;
@@ -1143,6 +1143,10 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
1143
1143
  const title = pathDisplay ? `Read ${pathDisplay}` : "Read";
1144
1144
  let cachedWidth: number | undefined;
1145
1145
  let cachedLines: string[] | undefined;
1146
+ const dropCache = () => {
1147
+ cachedWidth = undefined;
1148
+ cachedLines = undefined;
1149
+ };
1146
1150
  const expanded = this.#expanded;
1147
1151
  const component: Component = {
1148
1152
  render: (width: number) => {
@@ -1164,10 +1168,8 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
1164
1168
  cachedWidth = width;
1165
1169
  return cachedLines;
1166
1170
  },
1167
- invalidate: () => {
1168
- cachedWidth = undefined;
1169
- cachedLines = undefined;
1170
- },
1171
+ invalidate: dropCache,
1172
+ releaseRenderCaches: dropCache,
1171
1173
  };
1172
1174
  this.addChild(component);
1173
1175
  }
@@ -271,6 +271,12 @@ class SkillCallout implements Component {
271
271
  this.#lines = undefined;
272
272
  }
273
273
 
274
+ releaseRenderCaches(): void {
275
+ this.#source = undefined;
276
+ this.#lines = undefined;
277
+ this.#box.releaseRenderCaches();
278
+ }
279
+
274
280
  render(width: number): readonly string[] {
275
281
  const inner = this.#box.render(Math.max(1, width - 1));
276
282
  if (this.#source === inner && this.#lines !== undefined) return this.#lines;