@oh-my-pi/pi-tui 18.13.3 → 18.14.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 (236) hide show
  1. package/CHANGELOG.md +29 -41
  2. package/README.md +40 -34
  3. package/dist/types/app-keybindings.d.ts +15 -0
  4. package/dist/types/apps/session-picker.d.ts +1 -0
  5. package/dist/types/charts/chart-plan.d.ts +9 -7
  6. package/dist/types/chat/assistant-message.d.ts +14 -2
  7. package/dist/types/chat/extension-types.d.ts +31 -0
  8. package/dist/types/chat/figure-markdown.d.ts +2 -0
  9. package/dist/types/chat/reply-write.d.ts +48 -0
  10. package/dist/types/chat/svg-figure.d.ts +5 -0
  11. package/dist/types/chat/table-chart.d.ts +6 -3
  12. package/dist/types/chat/tool-execution.d.ts +0 -1
  13. package/dist/types/chat/transcript-outline.d.ts +7 -0
  14. package/dist/types/chat/transcript-render-helpers.d.ts +2 -0
  15. package/dist/types/chrome/index.d.ts +1 -0
  16. package/dist/types/chrome/transcript-notice.d.ts +35 -0
  17. package/dist/types/chrome/transcript-status.d.ts +20 -8
  18. package/dist/types/components/editor.d.ts +6 -0
  19. package/dist/types/components/image.d.ts +6 -0
  20. package/dist/types/components/markdown.d.ts +7 -0
  21. package/dist/types/native/blobs.d.ts +2 -1
  22. package/dist/types/native/picker.d.ts +3 -3
  23. package/dist/types/overlays/btw-history-panel.d.ts +2 -0
  24. package/dist/types/overlays/login-dialog.d.ts +5 -1
  25. package/dist/types/overlays/model-browser.d.ts +10 -1
  26. package/dist/types/overlays/oauth-selector.d.ts +23 -0
  27. package/dist/types/overlays/rewind-selector.d.ts +1 -1
  28. package/dist/types/overlays/session-selector.d.ts +9 -0
  29. package/dist/types/prompt/composer.d.ts +41 -4
  30. package/dist/types/prompt/interactive-context-helpers.d.ts +10 -3
  31. package/dist/types/prompt/welcome.d.ts +6 -2
  32. package/dist/types/render/extension-links.d.ts +65 -0
  33. package/dist/types/render/hyperlink.d.ts +25 -7
  34. package/dist/types/render/index.d.ts +1 -0
  35. package/dist/types/setup/scenes/model.d.ts +1 -1
  36. package/dist/types/setup/scenes/outro.d.ts +5 -4
  37. package/dist/types/setup/scenes/sign-in.d.ts +13 -8
  38. package/dist/types/setup/scenes/splash.d.ts +4 -4
  39. package/dist/types/setup/scenes/types.d.ts +16 -1
  40. package/dist/types/setup/startup-splash.d.ts +1 -1
  41. package/dist/types/setup/wizard-overlay.d.ts +11 -7
  42. package/dist/types/setup/wizard.d.ts +5 -1
  43. package/dist/types/status-line/component.d.ts +1 -1
  44. package/dist/types/status-line/context-usage.d.ts +28 -6
  45. package/dist/types/status-line/host.d.ts +6 -2
  46. package/dist/types/tools/bash.d.ts +2 -0
  47. package/dist/types/tools/default-renderer.d.ts +6 -4
  48. package/dist/types/tools/irc.d.ts +6 -0
  49. package/dist/types/tools/json-tree.d.ts +23 -4
  50. package/dist/types/tools/lsp.d.ts +2 -0
  51. package/dist/types/tools/mcp.d.ts +6 -5
  52. package/dist/types/tools/native-view.d.ts +1 -1
  53. package/dist/types/tools/renderer.d.ts +2 -0
  54. package/dist/types/tools/streaming-output.d.ts +11 -0
  55. package/dist/types/tools/task.d.ts +9 -1
  56. package/dist/types/tools/url-scheme-host.d.ts +17 -0
  57. package/dist/types/tools/yield.d.ts +1 -1
  58. package/dist/types/tui.d.ts +3 -1
  59. package/package.json +9 -14
  60. package/src/app-keybindings.ts +15 -0
  61. package/src/apps/session-picker.ts +2 -0
  62. package/src/charts/chart-plan.ts +20 -50
  63. package/src/chat/assistant-message.ts +134 -18
  64. package/src/chat/background-tan-message.ts +4 -0
  65. package/src/chat/chat-transcript-builder.ts +58 -28
  66. package/src/chat/compaction-summary-message.ts +2 -1
  67. package/src/chat/extension-types.ts +34 -0
  68. package/src/chat/figure-markdown.ts +6 -4
  69. package/src/chat/reply-write.ts +127 -0
  70. package/src/chat/svg-figure.ts +55 -16
  71. package/src/chat/table-chart.ts +29 -6
  72. package/src/chat/tool-execution.ts +13 -10
  73. package/src/chat/transcript-outline.ts +27 -0
  74. package/src/chat/transcript-render-helpers.ts +66 -24
  75. package/src/chrome/index.ts +1 -0
  76. package/src/chrome/transcript-notice.ts +83 -0
  77. package/src/chrome/transcript-status.ts +29 -29
  78. package/src/components/editor.ts +7 -0
  79. package/src/components/image.ts +13 -0
  80. package/src/components/markdown.ts +116 -6
  81. package/src/hotkeys-markdown.ts +1 -0
  82. package/src/native/backend.ts +3 -2
  83. package/src/native/blobs.ts +3 -2
  84. package/src/native/picker.ts +3 -3
  85. package/src/overlays/advisor-config.ts +1 -10
  86. package/src/overlays/btw-history-panel.ts +8 -0
  87. package/src/overlays/jobs-panel.ts +38 -4
  88. package/src/overlays/login-dialog.ts +31 -5
  89. package/src/overlays/model-browser.ts +37 -0
  90. package/src/overlays/model-hub.ts +1 -7
  91. package/src/overlays/model-picker.ts +1 -10
  92. package/src/overlays/oauth-selector.ts +44 -6
  93. package/src/overlays/rewind-selector.ts +29 -12
  94. package/src/overlays/session-selector.ts +113 -16
  95. package/src/overlays/tree-selector.ts +21 -13
  96. package/src/prompt/composer.ts +213 -5
  97. package/src/prompt/emoji-autocomplete.ts +0 -2
  98. package/src/prompt/interactive-context-helpers.ts +29 -4
  99. package/src/prompt/welcome.ts +31 -45
  100. package/src/render/extension-links.ts +214 -0
  101. package/src/render/hyperlink.ts +65 -9
  102. package/src/render/index.ts +1 -0
  103. package/src/setup/scenes/composer.ts +2 -0
  104. package/src/setup/scenes/glyph.ts +2 -0
  105. package/src/setup/scenes/model.ts +192 -29
  106. package/src/setup/scenes/outro.ts +30 -17
  107. package/src/setup/scenes/sign-in.ts +196 -400
  108. package/src/setup/scenes/splash.ts +25 -21
  109. package/src/setup/scenes/theme.ts +2 -0
  110. package/src/setup/scenes/types.ts +18 -1
  111. package/src/setup/startup-splash.ts +8 -7
  112. package/src/setup/wizard-overlay.ts +164 -33
  113. package/src/setup/wizard.ts +11 -2
  114. package/src/status-line/component.ts +4 -10
  115. package/src/status-line/context-usage.ts +72 -21
  116. package/src/status-line/host.ts +6 -2
  117. package/src/theme/defaults.json +1 -0
  118. package/src/theme/loader.ts +1 -1
  119. package/src/theme/theme.ts +2 -10
  120. package/src/tools/bash.ts +2 -0
  121. package/src/tools/default-renderer.ts +9 -6
  122. package/src/tools/eval.ts +14 -4
  123. package/src/tools/irc.ts +6 -0
  124. package/src/tools/json-tree.ts +131 -41
  125. package/src/tools/lsp.ts +36 -15
  126. package/src/tools/mcp.ts +20 -12
  127. package/src/tools/native-view.ts +1 -1
  128. package/src/tools/renderer.ts +2 -0
  129. package/src/tools/streaming-output.ts +41 -0
  130. package/src/tools/task.ts +59 -17
  131. package/src/tools/url-scheme-host.ts +21 -0
  132. package/src/tools/yield.ts +18 -9
  133. package/src/tui.ts +36 -13
  134. package/THIRD-PARTY-NOTICES.txt +0 -22901
  135. package/dist/types/theme/defaults/index.d.ts +0 -10289
  136. package/src/theme/defaults/alabaster.json +0 -103
  137. package/src/theme/defaults/amethyst.json +0 -106
  138. package/src/theme/defaults/anthracite.json +0 -103
  139. package/src/theme/defaults/basalt.json +0 -101
  140. package/src/theme/defaults/birch.json +0 -105
  141. package/src/theme/defaults/dark-abyss.json +0 -101
  142. package/src/theme/defaults/dark-arctic.json +0 -114
  143. package/src/theme/defaults/dark-aurora.json +0 -105
  144. package/src/theme/defaults/dark-catppuccin.json +0 -117
  145. package/src/theme/defaults/dark-cavern.json +0 -101
  146. package/src/theme/defaults/dark-celestial.json +0 -116
  147. package/src/theme/defaults/dark-copper.json +0 -105
  148. package/src/theme/defaults/dark-cosmos.json +0 -100
  149. package/src/theme/defaults/dark-cyberpunk.json +0 -112
  150. package/src/theme/defaults/dark-dracula.json +0 -108
  151. package/src/theme/defaults/dark-eclipse.json +0 -101
  152. package/src/theme/defaults/dark-ember.json +0 -105
  153. package/src/theme/defaults/dark-equinox.json +0 -100
  154. package/src/theme/defaults/dark-forest.json +0 -106
  155. package/src/theme/defaults/dark-github.json +0 -115
  156. package/src/theme/defaults/dark-gruvbox.json +0 -122
  157. package/src/theme/defaults/dark-lavender.json +0 -105
  158. package/src/theme/defaults/dark-lunar.json +0 -99
  159. package/src/theme/defaults/dark-midnight.json +0 -105
  160. package/src/theme/defaults/dark-monochrome.json +0 -104
  161. package/src/theme/defaults/dark-monokai.json +0 -108
  162. package/src/theme/defaults/dark-nebula.json +0 -100
  163. package/src/theme/defaults/dark-neon-noir.json +0 -110
  164. package/src/theme/defaults/dark-nord.json +0 -108
  165. package/src/theme/defaults/dark-ocean.json +0 -111
  166. package/src/theme/defaults/dark-one.json +0 -110
  167. package/src/theme/defaults/dark-poimandres.json +0 -125
  168. package/src/theme/defaults/dark-rainforest.json +0 -101
  169. package/src/theme/defaults/dark-reef.json +0 -101
  170. package/src/theme/defaults/dark-retro.json +0 -102
  171. package/src/theme/defaults/dark-rose-pine.json +0 -106
  172. package/src/theme/defaults/dark-sakura.json +0 -105
  173. package/src/theme/defaults/dark-slate.json +0 -105
  174. package/src/theme/defaults/dark-solarized.json +0 -107
  175. package/src/theme/defaults/dark-solstice.json +0 -100
  176. package/src/theme/defaults/dark-starfall.json +0 -101
  177. package/src/theme/defaults/dark-sunset.json +0 -109
  178. package/src/theme/defaults/dark-swamp.json +0 -100
  179. package/src/theme/defaults/dark-synthwave.json +0 -113
  180. package/src/theme/defaults/dark-taiga.json +0 -101
  181. package/src/theme/defaults/dark-terminal.json +0 -105
  182. package/src/theme/defaults/dark-tokyo-night.json +0 -111
  183. package/src/theme/defaults/dark-tundra.json +0 -101
  184. package/src/theme/defaults/dark-twilight.json +0 -101
  185. package/src/theme/defaults/dark-volcanic.json +0 -101
  186. package/src/theme/defaults/graphite.json +0 -102
  187. package/src/theme/defaults/index.ts +0 -203
  188. package/src/theme/defaults/light-arctic.json +0 -117
  189. package/src/theme/defaults/light-aurora-day.json +0 -101
  190. package/src/theme/defaults/light-canyon.json +0 -101
  191. package/src/theme/defaults/light-catppuccin.json +0 -116
  192. package/src/theme/defaults/light-cirrus.json +0 -100
  193. package/src/theme/defaults/light-coral.json +0 -105
  194. package/src/theme/defaults/light-cyberpunk.json +0 -106
  195. package/src/theme/defaults/light-dawn.json +0 -100
  196. package/src/theme/defaults/light-dunes.json +0 -101
  197. package/src/theme/defaults/light-eucalyptus.json +0 -105
  198. package/src/theme/defaults/light-forest.json +0 -110
  199. package/src/theme/defaults/light-frost.json +0 -105
  200. package/src/theme/defaults/light-github.json +0 -125
  201. package/src/theme/defaults/light-glacier.json +0 -101
  202. package/src/theme/defaults/light-gruvbox.json +0 -118
  203. package/src/theme/defaults/light-haze.json +0 -100
  204. package/src/theme/defaults/light-honeycomb.json +0 -105
  205. package/src/theme/defaults/light-lagoon.json +0 -101
  206. package/src/theme/defaults/light-lavender.json +0 -105
  207. package/src/theme/defaults/light-meadow.json +0 -101
  208. package/src/theme/defaults/light-mint.json +0 -105
  209. package/src/theme/defaults/light-monochrome.json +0 -111
  210. package/src/theme/defaults/light-ocean.json +0 -109
  211. package/src/theme/defaults/light-one.json +0 -109
  212. package/src/theme/defaults/light-opal.json +0 -101
  213. package/src/theme/defaults/light-orchard.json +0 -101
  214. package/src/theme/defaults/light-paper.json +0 -105
  215. package/src/theme/defaults/light-poimandres.json +0 -125
  216. package/src/theme/defaults/light-prism.json +0 -100
  217. package/src/theme/defaults/light-retro.json +0 -108
  218. package/src/theme/defaults/light-sand.json +0 -105
  219. package/src/theme/defaults/light-savanna.json +0 -101
  220. package/src/theme/defaults/light-solarized.json +0 -112
  221. package/src/theme/defaults/light-soleil.json +0 -100
  222. package/src/theme/defaults/light-sunset.json +0 -109
  223. package/src/theme/defaults/light-synthwave.json +0 -108
  224. package/src/theme/defaults/light-tokyo-night.json +0 -121
  225. package/src/theme/defaults/light-wetland.json +0 -101
  226. package/src/theme/defaults/light-zenith.json +0 -99
  227. package/src/theme/defaults/limestone.json +0 -104
  228. package/src/theme/defaults/mahogany.json +0 -107
  229. package/src/theme/defaults/marble.json +0 -103
  230. package/src/theme/defaults/obsidian.json +0 -101
  231. package/src/theme/defaults/onyx.json +0 -101
  232. package/src/theme/defaults/pearl.json +0 -103
  233. package/src/theme/defaults/porcelain.json +0 -101
  234. package/src/theme/defaults/quartz.json +0 -106
  235. package/src/theme/defaults/sandstone.json +0 -105
  236. package/src/theme/defaults/titanium.json +0 -100
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.14.1] - 2026-10-31
6
+
7
+ ### Added
8
+
9
+ - Added a `maxRows` callback to `FigureMarkdown`, `SvgFigure` and `TableChartFigure` so surfaces shorter than the screen can fit their figures; zero hides a figure without delaying transcript completion ([#15661](https://github.com/can1357/oh-my-pi/issues/15661), [#15853](https://github.com/can1357/oh-my-pi/pull/15853) by [@KunoLu](https://github.com/KunoLu))
10
+
11
+ ### Fixed
12
+
13
+ - Fixed IME composition text in the BTW history `Follow up:` input appearing at the left edge instead of at the caret when the hardware cursor is enabled ([#17427](https://github.com/can1357/oh-my-pi/issues/17427))
14
+ - Fixed session tree rows overlapping when messages, tool arguments, or labels contain carriage returns or Unicode line/paragraph separators (`U+2028`, `U+2029`), while keeping multiline formatting in the selected entry preview ([#17430](https://github.com/can1357/oh-my-pi/pull/17430) by [@lincicomb](https://github.com/lincicomb)).
15
+ - Fixed the Background jobs sheet dropping a finished job and its output while you were reading it; the selected job now stays until you select another ([#17506](https://github.com/can1357/oh-my-pi/pull/17506) by [@H4vC](https://github.com/H4vC))
16
+
17
+ ## [18.14.0] - 2026-10-29
18
+
19
+ ### Breaking Changes
20
+
21
+ - Moved `PINNED_HUD_TOGGLE_ID` to the coding agent's task output manager so reserving subagent IDs no longer loads the interactive editor.
22
+ - Updated `StatusLineHost.resetExpiryNotice` to receive the session and manage warning deduplication; custom status-line components must use the new signature.
23
+ - Removed `ToolExecutionHandle.setToolActivityVisible`; tool-activity visibility is now controlled by transcript components, while reply blocks remain visible by default.
24
+ - Removed the `@oh-my-pi/pi-tui/theme/defaults` export. Bundled palettes are now available through `getBuiltinThemes()` from `theme/defaults.json`.
25
+
26
+ ### Fixed
27
+
28
+ - Fixed table-chart direction detection so measures are not incorrectly marked as better based on header words; single-measure tables are now sent for judging when local charting is appropriate.
29
+ - Fixed LSP diagnostics that incorrectly showed “No issues” before a language server had reported; such files are now shown as unverified. ([#17293](https://github.com/can1357/oh-my-pi/pull/17293) by [@JustasMasiulis](https://github.com/JustasMasiulis))
30
+ - Fixed Esc-Esc rewind in Tern selecting interrupted turns with no visible output instead of the last displayed turn.
31
+ - Fixed streamed code blocks losing lines that scrolled off screen before the closing fence arrived; completed lines now remain available in scrollback. ([#17333](https://github.com/can1357/oh-my-pi/pull/17333) by [@gzkGit777](https://github.com/gzkGit777))
32
+
5
33
  ## [18.13.3] - 2026-10-28
6
34
 
7
35
  ### Added
@@ -2458,44 +2486,4 @@
2458
2486
  - Fixed a notification chip (or any injected block) rendering on top of an actively streaming tool render on ED3-risk terminals (Ghostty/kitty/Alacritty/iTerm2). While a foreground tool streams, its header's elapsed-time counter ticks every frame; once output scrolls the header above the viewport top, each tick is an offscreen edit that — because the eager scrollback-rebuild opt-in is gated off on these terminals — repaints the viewport in place and advances the rendered line count without committing the new overflow to native history. `#scrollbackHighWater` then lagged the logical viewport top, so a later content shrink whose changes landed in the visible region slipped past the shrink-across-boundary guard and reached the differential emitter, which is anchored to `#maxLinesRendered - height`: it rewrote only the suffix, dropped the newly exposed top row, and left a blank at the bottom, drifting every row below the edit one line up so it painted over the rows above. Such shrinks now re-anchor the bottom of the viewport with a non-destructive repaint, and the foreground-streaming shrink-across-boundary case repaints the live tail instead of padding and pinning the pre-shrink viewport.
2459
2487
  - Fixed a terminal resize during foreground-tool streaming on an unknown-viewport / ED3-risk host (Ghostty/kitty/Alacritty/iTerm2/WSL) leaving native scrollback permanently out of sync, so scrolling back after the turn showed missing rows. A pure geometry resize (no content change) takes the in-place viewport-repaint path, which — unlike a content-bearing resize that rebuilds via the geometry branch — never flagged native history. Because the prompt-submit checkpoint (`refreshNativeScrollbackIfDirty`) only rebuilds when scrollback is marked dirty on these hosts, the discrepancy was never reconciled. Overflowing geometry repaints whose viewport is not known to be at the bottom now mark scrollback dirty so the next checkpoint rebuilds an exact copy of the transcript.
2460
2488
 
2461
- ## [15.8.2] - 2026-06-03
2462
-
2463
- ### Added
2464
-
2465
- - Added `PI_NO_SYNC_OUTPUT=1` to disable DEC 2026 synchronized-output wrappers for terminals whose implementation is buggy or visually worse, while keeping the renderer's autowrap guards active during paints ([#1765](https://github.com/can1357/oh-my-pi/issues/1765)).
2466
-
2467
- ### Fixed
2468
-
2469
- - Fixed terminal resizes that land in the same render frame as streamed output splicing a phantom blank row into native scrollback and offsetting every later row by one. A height shrink (or width change carrying an append) with content overflowing the viewport fell through to the differential emitter, whose scroll math is anchored to the pre-resize viewport top and hardware-cursor row — both invalidated by the terminal's own resize reflow. Geometry-changed frames now rebuild native history when the viewport is at (or possibly at) the bottom, and defer non-destructively for a reader confirmed scrolled into history.
2470
- - Fixed Ghostty/kitty/Alacritty-style ED3-risk terminals freezing the prompt after a deferred shrink; focused keyboard input now uses the same explicit user-input viewport opt-in as autocomplete and can repaint immediately instead of waiting for a resize.
2471
- - Deferred eager live scrollback rebuilds under WSL fronted by Windows Terminal (`WT_SESSION` present in a Linux environment) so foreground streaming no longer emits ED3 (`CSI 3 J`) and yanks a reader scrolled into Windows Terminal's host scrollback; deferred rewrites still reconcile at the next prompt-submit checkpoint ([#1610](https://github.com/can1357/oh-my-pi/issues/1610)).
2472
- - Fixed tmux (and screen/zellij) pane history gaining a complete duplicate copy of the transcript every time a deferred offscreen edit was followed by another render. Multiplexer panes never receive a destructive scrollback clear, so the dirty-scrollback rebuild path only appended the full transcript on top of preserved pane history — repeatedly. Live frames inside multiplexers now keep repainting the viewport and leave history reconciliation to explicit checkpoints, which also removes the O(transcript) write amplification per frame.
2473
- - Fixed tmux pane viewports corrupting and pane history duplicating when a resize coincides with rendering: a resize racing a streamed append reached the stale-anchor diff emitters (phantom rows in the pane), a forced render racing a resize replayed the whole transcript into preserved pane history, and the prompt-submit checkpoint did the same after any deferred offscreen edit. Geometry-changed frames inside multiplexers now repaint the viewport in place, and forced-render geometry replays plus checkpoint replays are disabled there — tmux reflows its own pane grid and its history cannot be cleared, only duplicated.
2474
- - Fixed terminal resize events whose dimensions net out unchanged by render time (rapid SIGWINCH round trips during a window drag, coalesced into one 16ms frame) being invisible to the renderer. The terminal reflows its buffer on every resize event — rows move between the viewport and scrollback and can be evicted at the scrollback cap — so diffing against the pre-resize screen splices blank phantom rows into the viewport. The renderer now tracks the resize event itself, not just the dimension delta, and routes such frames through the geometry-change repaint/rebuild paths.
2475
- - Fixed Termux terminal resizes (screen rotation or software-keyboard toggles) displacing or hiding output after the viewport height changed. Content-bearing resizes were routed to the differential emitter, whose scroll math is anchored to the pre-resize viewport, so appended rows landed too low; pure height changes were treated as no-ops, exposing blank rows that later appends could fill without growing native scrollback. Termux resizes now repaint or rebuild at the new geometry like every other non-multiplexer terminal.
2476
- - Fixed the turn-end teardown frame freezing on ED3-risk terminals (Ghostty/kitty/Alacritty/iTerm2): disabling eager scrollback rebuild now takes effect only after the in-flight frame is classified, so the loader/status removal still paints instead of deferring and leaving a stale spinner until the next keystroke.
2477
- - Fixed non-WT ConPTY terminals on Windows (Tabby, Hyper, VS Code, conhost) clearing scrollback and yanking the viewport to the top whenever streaming output or a prompt-submit rebuild arrived while the user was scrolled up. The kernel32 viewport probe describes the ConPTY pseudo-console buffer — which is pinned to the visible grid, invisible to host-UI scrollback — so it reported "at bottom" no matter where the user had scrolled, and the [#1635](https://github.com/can1357/oh-my-pi/issues/1635) fix only distrusted it under `WT_SESSION`, which Tabby and other ConPTY hosts never set. The probe is now removed entirely: every Windows host is treated as viewport-unobservable, live mutations defer destructive rebuilds (no `\x1b[3J`, no viewport movement), and native scrollback reconciles at the prompt-submit checkpoint where the Enter keystroke has already pinned the host viewport to the bottom ([#1746](https://github.com/can1357/oh-my-pi/issues/1746)).
2478
- - Fixed emoji-presentation symbols (a default-text symbol followed by variation-selector-16 `U+FE0F`, e.g. `⚠️`, `ℹ️`, `❤️`, keycaps) measuring as 1 cell instead of 2 in the native width engine on macOS. The native scanner now keeps `UnicodeWidthStr` as the source of truth for multi-codepoint graphemes and applies only the local macOS Hangul Compatibility Jamo character-width delta, preserving VS16/keycap sequence widths without reintroducing jamo cursor drift.
2479
- - Deferred eager live scrollback rebuilds on macOS Terminal.app and iTerm2 so assistant/tool streaming no longer emits ED3 (`CSI 3 J`) while their native viewport position is unobservable, preserving readers scrolled into terminal history ([#1300](https://github.com/can1357/oh-my-pi/issues/1300)).
2480
- - Fixed width-shrink reflow leaving old-width rows in native history so later appends no longer undercount scrollback growth or duplicate wrapped content.
2481
- - Fixed hiding overlays after terminal reflow so stale dialog rows are scrubbed from native scrollback on non-multiplexer terminals.
2482
-
2483
- ### Removed
2484
-
2485
- - Removed `shouldTrustNativeViewportProbe` and `ProcessTerminal`'s kernel32 `GetConsoleScreenBufferInfo` viewport probe. No Windows environment can answer "is the user's viewport at the bottom" truthfully — under ConPTY (every modern host) the pseudo-console buffer is pinned to the visible grid so the probe always read "at bottom", and under legacy conhost the window tracks the output cursor rather than the buffer tail so it always read "scrolled up" — so the probe and its trust gate are gone; `ProcessTerminal` no longer implements the optional `Terminal.isNativeViewportAtBottom`.
2486
-
2487
- ## [15.8.1] - 2026-06-02
2488
-
2489
- ### Fixed
2490
-
2491
- - Deferred eager live scrollback rebuilds on VTE terminals so GNOME-style Linux terminals do not flash or erase readable scrollback during streaming ([#1719](https://github.com/can1357/oh-my-pi/issues/1719)).
2492
-
2493
- ## [15.8.0] - 2026-06-02
2494
-
2495
- ### Fixed
2496
-
2497
- - Deferred eager live scrollback rebuilds on POSIX terminals where xterm ED3 (`CSI 3 J`, erase saved lines) can disturb scrolled-up readers during streaming, while keeping direct user-input and checkpoint rebuilds explicit ([#1682](https://github.com/can1357/oh-my-pi/issues/1682)).
2498
- - Fixed TUI shutdown placing the parent shell prompt one row below short rendered content instead of directly on the next line ([#1620](https://github.com/can1357/oh-my-pi/issues/1620)).
2499
- - Stopped painting inline color swatches for 4-digit hex runs in Markdown rendering. The `#RGBA` CSS form collides with hashline `#TAG` snapshot tags (4 hex digits, e.g. `#6C5E`), which were sprouting spurious RGB swatches in prose and codespans. Only `#RGB`, `#RRGGBB`, and `#RRGGBBAA` qualify now.
2500
-
2501
- Older entries are archived in [packages/tui/CHANGELOG.md@3deb6751027b](https://github.com/can1357/oh-my-pi/blob/3deb6751027b11b10ac14b2a5c07303817276505/packages/tui/CHANGELOG.md).
2489
+ Older entries are archived in [packages/tui/CHANGELOG.md@6693ddbfd58c](https://github.com/can1357/oh-my-pi/blob/6693ddbfd58ce9edcdc0dc3fe98a8882c9bae834/packages/tui/CHANGELOG.md).
package/README.md CHANGED
@@ -28,7 +28,7 @@ const tui = new TUI(terminal);
28
28
  tui.addChild(new Text("Welcome to my app!"));
29
29
 
30
30
  const editor = new Editor(editorTheme);
31
- editor.onSubmit = (text) => {
31
+ editor.onSubmit = text => {
32
32
  console.log("Submitted:", text);
33
33
  tui.addChild(new Text(`You said: ${text}`));
34
34
  };
@@ -106,12 +106,12 @@ interface Component {
106
106
  }
107
107
  ```
108
108
 
109
- | Method | Description |
110
- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
109
+ | Method | Description |
110
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
111
  | `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. |
112
- | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
113
- | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
114
- | `releaseRenderCaches?()` | Drops only derived render caches. The next `render()` must return the same rows without eager rebuilds, renderer callbacks, image conversions, or child replacement. |
112
+ | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
113
+ | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
114
+ | `releaseRenderCaches?()` | Drops only derived render caches. The next `render()` must return the same rows without eager rebuilds, renderer callbacks, image conversions, or child replacement. |
115
115
 
116
116
  ## Built-in Components
117
117
 
@@ -119,20 +119,20 @@ interface Component {
119
119
 
120
120
  Build screens from persistent components and update their data, selection, expansion, or size through setters. Layouts own child bounds and mouse-coordinate translation; controllers retain domain workflows and asynchronous operations. Propagate `invalidate()` after theme changes and `dispose()` when removing an owned component tree.
121
121
 
122
- | Family | Components | Import |
123
- | --- | --- | --- |
124
- | Layout | `Stack`, `Row`, `SplitPane` | package root |
125
- | Panels | `OverlayPanel`, `PanelRows`, `PanelDivider` | `/chrome` |
126
- | Menus | `SelectList`, `MenuSelection` | package root |
127
- | Forms | `Form`, `FormField`, `TextFormField`, `SelectFormField`, `SettingsFormField` | package root |
128
- | Wizard steps | `WizardStep` | package root |
129
- | Viewports | `ScrollView`, including child rendering, follow-tail, and keyed range anchoring | package root |
130
- | Trees | `TreeView` | package root |
131
- | Disclosure | `Disclosure`, with lazy summary/detail children | package root |
132
- | Messages | `FramedMessageComponent`, `MessageNoticeComponent`, `MessageDividerComponent` | `/chrome` |
133
- | Tool output | `ToolCard`, `framedToolCard`, `plainToolCard`, `OutputPane` | `/render` |
134
- | Transcripts | `TranscriptBrowser` | package root |
135
- | Data | `MetricRow`, `ProgressBar`, `Table`, `KeyValueList`, `Section` | package root |
122
+ | Family | Components | Import |
123
+ | ------------ | ------------------------------------------------------------------------------- | ------------ |
124
+ | Layout | `Stack`, `Row`, `SplitPane` | package root |
125
+ | Panels | `OverlayPanel`, `PanelRows`, `PanelDivider` | `/chrome` |
126
+ | Menus | `SelectList`, `MenuSelection` | package root |
127
+ | Forms | `Form`, `FormField`, `TextFormField`, `SelectFormField`, `SettingsFormField` | package root |
128
+ | Wizard steps | `WizardStep` | package root |
129
+ | Viewports | `ScrollView`, including child rendering, follow-tail, and keyed range anchoring | package root |
130
+ | Trees | `TreeView` | package root |
131
+ | Disclosure | `Disclosure`, with lazy summary/detail children | package root |
132
+ | Messages | `FramedMessageComponent`, `MessageNoticeComponent`, `MessageDividerComponent` | `/chrome` |
133
+ | Tool output | `ToolCard`, `framedToolCard`, `plainToolCard`, `OutputPane` | `/render` |
134
+ | Transcripts | `TranscriptBrowser` | package root |
135
+ | Data | `MetricRow`, `ProgressBar`, `Table`, `KeyValueList`, `Section` | package root |
136
136
 
137
137
  ```typescript
138
138
  import { Disclosure, SplitPane, Text } from "@oh-my-pi/pi-tui";
@@ -176,10 +176,10 @@ Container that applies padding and background color to all children.
176
176
  const box = new Box(
177
177
  1, // paddingX (default: 1)
178
178
  1, // paddingY (default: 1)
179
- (text) => chalk.bgGray(text), // optional background function
179
+ text => chalk.bgGray(text), // optional background function
180
180
  );
181
181
  box.addChild(new Text("Content"));
182
- box.setBgFn((text) => chalk.bgBlue(text)); // Change background dynamically
182
+ box.setBgFn(text => chalk.bgBlue(text)); // Change background dynamically
183
183
  ```
184
184
 
185
185
  ### Text
@@ -191,10 +191,10 @@ const text = new Text(
191
191
  "Hello World", // text content
192
192
  1, // paddingX (default: 1)
193
193
  1, // paddingY (default: 1)
194
- (text) => chalk.bgGray(text), // optional background function
194
+ text => chalk.bgGray(text), // optional background function
195
195
  );
196
196
  text.setText("Updated text");
197
- text.setCustomBgFn((text) => chalk.bgBlue(text));
197
+ text.setCustomBgFn(text => chalk.bgBlue(text));
198
198
  ```
199
199
 
200
200
  ### TruncatedText
@@ -215,7 +215,7 @@ Single-line text input with horizontal scrolling.
215
215
 
216
216
  ```typescript
217
217
  const input = new Input();
218
- input.onSubmit = (value) => console.log(value);
218
+ input.onSubmit = value => console.log(value);
219
219
  input.setValue("initial");
220
220
  input.getValue();
221
221
  ```
@@ -285,11 +285,11 @@ interface EditorTheme {
285
285
  }
286
286
 
287
287
  const editor = new Editor(theme);
288
- editor.onSubmit = (text) => console.log(text);
289
- editor.onChange = (text) => console.log("Changed:", text);
288
+ editor.onSubmit = text => console.log(text);
289
+ editor.onChange = text => console.log("Changed:", text);
290
290
  editor.disableSubmit = true; // Disable submit temporarily
291
291
  editor.setAutocompleteProvider(provider);
292
- editor.borderColor = (s) => chalk.blue(s); // Change border dynamically
292
+ editor.borderColor = s => chalk.blue(s); // Change border dynamically
293
293
  ```
294
294
 
295
295
  **Features:**
@@ -371,8 +371,8 @@ Animated loading spinner.
371
371
  ```typescript
372
372
  const loader = new Loader(
373
373
  tui, // TUI instance for render updates
374
- (s) => chalk.cyan(s), // spinner color function
375
- (s) => chalk.gray(s), // message color function
374
+ s => chalk.cyan(s), // spinner color function
375
+ s => chalk.gray(s), // message color function
376
376
  "Loading...", // message (default: "Loading...")
377
377
  );
378
378
  loader.start();
@@ -387,8 +387,8 @@ Extends Loader with Escape key handling and an AbortSignal for cancelling async
387
387
  ```typescript
388
388
  const loader = new CancellableLoader(
389
389
  tui, // TUI instance for render updates
390
- (s) => chalk.cyan(s), // spinner color function
391
- (s) => chalk.gray(s), // message color function
390
+ s => chalk.cyan(s), // spinner color function
391
+ s => chalk.gray(s), // message color function
392
392
  "Working...", // message
393
393
  );
394
394
  loader.onAbort = () => done(null); // Called when user presses Escape
@@ -430,9 +430,9 @@ const list = new SelectList(
430
430
  theme, // SelectListTheme
431
431
  );
432
432
 
433
- list.onSelect = (item) => console.log("Selected:", item);
433
+ list.onSelect = item => console.log("Selected:", item);
434
434
  list.onCancel = () => console.log("Cancelled");
435
- list.onSelectionChange = (item) => console.log("Highlighted:", item);
435
+ list.onSelectionChange = item => console.log("Highlighted:", item);
436
436
  list.setFilter("opt"); // Filter items
437
437
  ```
438
438
 
@@ -521,6 +521,12 @@ SIXEL images encode off the JavaScript thread: the first render reserves the ima
521
521
 
522
522
  Supported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.
523
523
 
524
+ ### Figures
525
+
526
+ `FigureMarkdown`, `SvgFigure`, and `TableChartFigure` size their images to a share of the terminal viewport. A surface shorter than the screen (an overlay, a pane) passes `maxRows: () => rows`, read on every render: the rows each figure may fill, a table chart's separator included. Zero hides a figure and keeps it from holding up transcript completion. Callers reserve room for prose and chrome themselves.
527
+
528
+ Lowering the ceiling refits the drawn image at once and redraws it at the new size; a SIXEL reserves its rows while each re-encode runs off the JavaScript thread, as for a width change. Changing the ceiling of rows already committed to native scrollback forces a resync, so leave committed figures alone.
529
+
524
530
  ## Autocomplete
525
531
 
526
532
  ### CombinedAutocompleteProvider
@@ -39,10 +39,13 @@ interface AppKeybindings {
39
39
  "app.session.rename": true;
40
40
  "app.session.delete": true;
41
41
  "app.session.deleteNoninvasive": true;
42
+ "app.session.pin": true;
43
+ "app.session.unpin": true;
42
44
  "app.tree.previousBranch": true;
43
45
  "app.tree.nextBranch": true;
44
46
  "app.plan.toggle": true;
45
47
  "app.history.search": true;
48
+ "app.links.hint": true;
46
49
  "app.stt.pushToTalk": true;
47
50
  "app.stt.toggle": true;
48
51
  "app.live.toggle": true;
@@ -329,6 +332,14 @@ export declare const KEYBINDINGS: {
329
332
  readonly defaultKeys: "ctrl+backspace";
330
333
  readonly description: "Delete session (non-invasive)";
331
334
  };
335
+ readonly "app.session.pin": {
336
+ readonly defaultKeys: "alt+k";
337
+ readonly description: "Pin the selected session in the resume picker";
338
+ };
339
+ readonly "app.session.unpin": {
340
+ readonly defaultKeys: "alt+shift+k";
341
+ readonly description: "Unpin the selected session in the resume picker";
342
+ };
332
343
  readonly "app.tree.previousBranch": {
333
344
  readonly defaultKeys: [];
334
345
  readonly description: "Jump to the previous branch in /tree";
@@ -345,6 +356,10 @@ export declare const KEYBINDINGS: {
345
356
  readonly defaultKeys: "ctrl+r";
346
357
  readonly description: "Search history";
347
358
  };
359
+ readonly "app.links.hint": {
360
+ readonly defaultKeys: "alt+o";
361
+ readonly description: "Pick an extension link on screen by its hint tag";
362
+ };
348
363
  readonly "app.stt.pushToTalk": {
349
364
  readonly defaultKeys: "space";
350
365
  readonly description: "Hold to record speech-to-text";
@@ -6,6 +6,7 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
6
6
  deleteSession?(session: T): Promise<boolean>;
7
7
  /** Rename the session and resolve with the title as stored, or null when it did not happen. */
8
8
  renameSession?(session: T, title: string): Promise<string | null>;
9
+ setSessionPinned?(session: T, pinned: boolean): Promise<void>;
9
10
  loadAllSessions?(): Promise<T[]>;
10
11
  }
11
12
  /** Presentation and capability controls for the standalone session picker. */
@@ -419,9 +419,10 @@ export type ChartAnnotation = {
419
419
  export declare function planChart(table: TableAnalysis): ChartPlan | undefined;
420
420
  /**
421
421
  * Whether a model should pick `table`'s chart (`tui.autoGraph: smart`):
422
- * multi-series tables, and tables whose rows may be named by a number the
423
- * `guess` plots (`Thread | State`, `Finding | Current`), where only meaning
424
- * tells a name from a measure.
422
+ * multi-series tables, tables whose rows may be named by a number the
423
+ * `guess` plots (`Thread | State`, `Finding | Current`), and any other chart
424
+ * the guess would draw — only meaning tells a measure from page references,
425
+ * step numbers or figures quoted in prose, which the judge charts as none.
425
426
  */
426
427
  export declare function shouldJudge(table: TableAnalysis, guess: ChartPlan | undefined): boolean;
427
428
  /** Measure columns before `guess`'s text label column: numbers that may only name their rows. */
@@ -446,10 +447,11 @@ export declare function tableTakeaways(table: TableAnalysis, plan: ChartPlan, co
446
447
  */
447
448
  export declare function drawnNames(spec: ChartSpec): Set<string>;
448
449
  /**
449
- * The local guess at what `plan`'s rows and measures mean: header keywords
450
- * for each measure's {@link Polarity} (resources like tokens or memory only
451
- * beside a baseline), rivals from the label's header, focus from bolded or
452
- * `(current)` row names, and a status column whose cells vary.
450
+ * The local guess at what `plan`'s rows and measures mean: each measure's
451
+ * {@link Polarity} as far as its unit says ({@link dimPolarity}; what a name
452
+ * means — `Memory` as a capacity or a cost — is the judge's call), rivals from
453
+ * the label's header, focus from bolded or `(current)` row names, and a status
454
+ * column whose cells vary.
453
455
  */
454
456
  export declare function guessMeaning(table: TableAnalysis, plan: ChartPlan): Pick<ChartPlan, "polarity" | "rowPolarity" | "rivals" | "focus" | "status">;
455
457
  /** Whether `plan`'s rows may each be a metric of their own: a `change`, a transposed table, or mixed-unit columns. */
@@ -1,7 +1,7 @@
1
1
  import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { type Component, Container } from "../tui.js";
3
3
  import { type ImageBudget } from "../components/image.js";
4
- import type { AssistantThinkingRenderer } from "./extension-types.js";
4
+ import type { AssistantTextRenderer, AssistantTextRendering, AssistantThinkingRenderer } from "./extension-types.js";
5
5
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
6
6
  import { type CacheInvalidation } from "./cache-invalidation-marker.js";
7
7
  import { type ServedModelMismatch } from "./served-model-marker.js";
@@ -38,7 +38,7 @@ export declare class AssistantMessageComponent extends Container {
38
38
  * for lack of a target is re-rendered with its reaction stripped.
39
39
  */
40
40
  pickReactionTarget(transcript: readonly Component[]): void;
41
- constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>, expandThinkingBlocks?: boolean);
41
+ constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>, expandThinkingBlocks?: boolean, textRendering?: AssistantTextRendering);
42
42
  /**
43
43
  * Show or clear the trailing cache-invalidation divider. Set at `message_end`
44
44
  * (live) or during rebuild, once the turn's usage is known and compared
@@ -122,7 +122,19 @@ export declare class AssistantMessageComponent extends Container {
122
122
  renderTranscriptBlockEmergencyRow(width: number): string | undefined;
123
123
  getTranscriptBlockVersion(): number;
124
124
  markTranscriptBlockFinalized(): void;
125
+ /**
126
+ * Swap the extension text renderers (the setting enabling them changed) and redraw with them.
127
+ * Enabling them also drops rows already published as stable, since a renderer may rewrite them;
128
+ * callers MUST pair this with a scrollback-clearing display reset, as for
129
+ * {@link resetTranscriptStableRows}.
130
+ */
131
+ setTextRenderers(renderers: readonly AssistantTextRenderer[]): void;
125
132
  applyRetryRecovery(retryRecovery: AssistantMessage["retryRecovery"]): void;
133
+ /**
134
+ * Draw the current message again when it is the reply sent at `timestamp`, re-running the
135
+ * extension text renderers; returns whether it was. The caller schedules the frame.
136
+ */
137
+ rerenderMessage(timestamp: number): boolean;
126
138
  messagePersistenceKey(): string | undefined;
127
139
  /** Toggle rendering for assistant-native and tool-result images. */
128
140
  setImagesVisible(visible: boolean): void;
@@ -1,3 +1,4 @@
1
+ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
1
2
  import type { Component, TUI } from "../tui.js";
2
3
  import type { Theme } from "../theme/theme.js";
3
4
  import type { CustomMessage, HookMessage } from "./messages.js";
@@ -17,6 +18,36 @@ export interface AssistantThinkingRenderContext {
17
18
  requestRender(): void;
18
19
  }
19
20
  export type AssistantThinkingRenderer = (context: AssistantThinkingRenderContext, theme: Theme) => Component | undefined;
21
+ export interface AssistantTextRenderContext {
22
+ /**
23
+ * The message being drawn; `message.timestamp` identifies the reply for `ctx.ui.rerenderMessage()`.
24
+ * After a tool call this is only the segment drawn there, so its `content` does not hold the whole reply.
25
+ * A deep copy: editing it, at any depth, cannot change the stored reply.
26
+ */
27
+ message: AssistantMessage;
28
+ /**
29
+ * The text block's index in the full reply's `content`, also when the reply is drawn in segments
30
+ * around tool calls; not an index into `message.content` for a segment. It counts the content as
31
+ * currently stored: a transcript rebuild that drops tool calls left without a result shifts later
32
+ * blocks down, so do not persist it as a permanent key.
33
+ */
34
+ contentIndex: number;
35
+ /** The block's markdown as drawn so far, after any earlier renderer. */
36
+ text: string;
37
+ /** True while the block may still grow; false for its final render, a block sealed at a tool call, and later redraws. */
38
+ streaming: boolean;
39
+ }
40
+ /**
41
+ * Display-only transform of one assistant text block: returns the markdown to draw, or `undefined`
42
+ * to draw `context.text` unchanged. The session, exports and model context keep the original text.
43
+ */
44
+ export type AssistantTextRenderer = (context: AssistantTextRenderContext) => string | undefined;
45
+ /** Extension text renderers for one assistant component, and where its content starts in the full reply. */
46
+ export interface AssistantTextRendering {
47
+ renderers: readonly AssistantTextRenderer[];
48
+ /** Index in the full reply's `content` of this component's first block; a segment after a tool call starts past it. */
49
+ contentOffset: number;
50
+ }
20
51
  export interface HookMessageRenderOptions {
21
52
  /** Whether the view is expanded */
22
53
  expanded: boolean;
@@ -8,6 +8,8 @@ export interface FigureMarkdownOptions {
8
8
  charts: boolean;
9
9
  /** Shared inline-image budget for the figures. */
10
10
  budget?: ImageBudget;
11
+ /** Rows each figure may fill, read on every render; callers reserve prose, chrome and separators themselves. Unset or `undefined` takes a share of the terminal viewport. */
12
+ maxRows?: () => number | undefined;
11
13
  /** A figure's rows changed outside a text update (raster landed, fell back to code). */
12
14
  onChange: () => void;
13
15
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Replies: text the agent sends to a scheme whose writes are `reply`-scoped (`out://`)
3
+ * is its mid-turn message to the user, drawn as assistant prose so it can answer
4
+ * without ending its turn.
5
+ * - A `write` call to a reply URL draws as {@link ReplyWriteComponent}, streaming as the
6
+ * call's arguments arrive, instead of a tool card.
7
+ * - Text a tool's commands write there (`printf … > out://` in bash) arrives in the
8
+ * tool's result `details.replies` ({@link toolResultReplies}); transcripts draw each
9
+ * one as a prose block after the call ({@link replyDisplayMessage}).
10
+ */
11
+ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
12
+ import type { ImageBudget } from "../components/image.js";
13
+ import { AssistantMessageComponent } from "./assistant-message.js";
14
+ import type { ToolExecutionHandle, ToolResultFrame } from "./tool-execution.js";
15
+ /**
16
+ * Whether a `toolName` call writes to a reply URL. `undefined` while streamed args cannot tell yet:
17
+ * no path so far, or a path that could still grow into a reply URL (`ou`, `out:/`). Callers holding
18
+ * final args treat `undefined` as `false`.
19
+ */
20
+ export declare function isReplyWrite(toolName: string, args: unknown): boolean | undefined;
21
+ /** Display options of a reply block; the rest of its presentation follows the assistant prose defaults. */
22
+ export interface ReplyWriteOptions {
23
+ /** Repaint request for figures and images that finish loading after a render. */
24
+ readonly requestRender?: () => void;
25
+ readonly imageBudget?: ImageBudget;
26
+ /** Resolved local destinations of Markdown links, as for assistant text. */
27
+ readonly linkTargets?: ReadonlyMap<string, string>;
28
+ }
29
+ /** Replies a tool result carries in `details.replies` (text its commands wrote to a reply URL), oldest first. */
30
+ export declare function toolResultReplies(details: unknown): readonly string[];
31
+ /** Assistant message drawing reply text. No provider turn backs it, so its identity fields stay empty. */
32
+ export declare function replyDisplayMessage(text: string, errorMessage?: string): AssistantMessage;
33
+ /**
34
+ * A reply write ({@link isReplyWrite}) drawn as assistant prose. The call's lifecycle arrives
35
+ * through {@link ToolExecutionHandle}: streamed args grow the text, final args settle it, and
36
+ * the result seals the block, a failure showing its error under the text. It is a reply, not
37
+ * tool activity, so hiding tool activity leaves it on screen.
38
+ */
39
+ export declare class ReplyWriteComponent extends AssistantMessageComponent implements ToolExecutionHandle {
40
+ #private;
41
+ constructor(options?: ReplyWriteOptions);
42
+ updateArgs(args: unknown): void;
43
+ setArgsComplete(): void;
44
+ setExecutionStarted(): void;
45
+ updateResult(result: ToolResultFrame, isPartial?: boolean): void;
46
+ parkAsBackground(): void;
47
+ seal(): void;
48
+ }
@@ -16,6 +16,11 @@ export interface SvgFigureOptions {
16
16
  markdown?: (text: string) => Markdown;
17
17
  /** Shared inline-image budget; superseded rasters are released from it. */
18
18
  budget?: ImageBudget;
19
+ /**
20
+ * Rows the figure may fill, fallback code included, read on every render;
21
+ * zero hides it. Unset or `undefined` takes a share of the terminal viewport.
22
+ */
23
+ maxRows?: () => number | undefined;
19
24
  /** The figure's rows changed outside a text update: a raster landed or rendering fell back to code. */
20
25
  onChange: () => void;
21
26
  }
@@ -19,10 +19,11 @@ export interface TableChartRequest {
19
19
  readonly guess: ChartPlan;
20
20
  }
21
21
  /**
22
- * Picks the chart for a multi-series table under `smart`: a plan, or `null`
23
- * for no chart. A rejection falls back to the local guess.
22
+ * Picks the chart for a table under `smart`: a plan, or `null` for no chart.
23
+ * A rejection falls back to the local guess; `undefined` (no judge to ask
24
+ * right now) keeps the guess at once, exactly as `always` would draw it.
24
25
  */
25
- export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null>;
26
+ export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null> | undefined;
26
27
  /**
27
28
  * The chart under a table: its SVG, colored with figure tokens (`var(--accent)`, …)
28
29
  * still to resolve; none; or a smart pick on its way (the promise settles once the cache holds it).
@@ -63,6 +64,8 @@ export declare function chartFence(svg: string): string;
63
64
  export interface TableChartFigureOptions {
64
65
  /** Shared inline-image budget. */
65
66
  budget?: ImageBudget;
67
+ /** Rows the chart may fill, its separator included, read on every render; under two hides it. Unset or `undefined` takes a share of the terminal viewport. */
68
+ maxRows?: () => number | undefined;
66
69
  /** The chart's rows changed outside a text update: its pick or raster landed. */
67
70
  onChange: () => void;
68
71
  }
@@ -35,7 +35,6 @@ export interface ToolExecutionHandle extends Component {
35
35
  setArgsComplete(toolCallId?: string): void;
36
36
  setExecutionStarted(toolCallId?: string): void;
37
37
  setExpanded(expanded: boolean): void;
38
- setToolActivityVisible(visible: boolean): void;
39
38
  /** Mark the call parked: it returned, but stays tracked for async job frames. */
40
39
  parkAsBackground(): void;
41
40
  /** Seal the block as final history and stop its animations. */
@@ -9,6 +9,7 @@ import { type TranscriptEntryLike as TranscriptEntry } from "./transcript-entry.
9
9
  import type { SessionMessageEntryLike as SessionMessageEntry } from "./transcript-entry.js";
10
10
  import { type ThemeColor } from "../theme/index.js";
11
11
  import type { ChatTranscriptBuilder } from "./chat-transcript-builder.js";
12
+ import type { DescribeContext } from "../native/node.js";
12
13
  /** One selectable transcript item: a message entry plus its rendered block range. */
13
14
  export interface OutlineTarget {
14
15
  /** Entry the selection resolves to; extended over trailing componentless tool results. */
@@ -63,6 +64,12 @@ export declare class OutlineRowCache {
63
64
  export declare function appendOutlineEntries(builder: ChatTranscriptBuilder, entries: TranscriptEntry[]): OutlineTarget[];
64
65
  /** Per-target "renders at least one non-blank row" flags at the given rows. */
65
66
  export declare function outlineVisibility(childRows: readonly (readonly string[])[], targets: readonly OutlineTarget[]): boolean[];
67
+ /**
68
+ * Per-target "describes something that shows" flags: {@link outlineVisibility}
69
+ * for a TSP page, where blocks describe nodes instead of rendering rows. An
70
+ * empty `col`/`row` (an interrupted turn with no output) shows nothing.
71
+ */
72
+ export declare function describedVisibility(blocks: readonly Component[], targets: readonly OutlineTarget[], cx: DescribeContext): boolean[];
66
73
  /** Dotted horizontal rule with rounded corners, spanning the outline width. */
67
74
  export declare function outlineRule(left: string, right: string, innerWidth: number, color?: ThemeColor, caption?: string): string;
68
75
  /** Wrap pre-rendered rows in the dotted outline (rules above/below, `┆` sides). */
@@ -55,6 +55,8 @@ export declare function assistantHasVisibleContent(message: AssistantAgentMessag
55
55
  export declare function splitAssistantMessageToolTimeline(message: AssistantAgentMessage): {
56
56
  beforeTools: AssistantAgentMessage;
57
57
  afterToolCalls: ReadonlyMap<string, AssistantAgentMessage>;
58
+ /** Index in `message.content` of each after-tool segment's first block, by tool-call id. */
59
+ contentOffsets: ReadonlyMap<string, number>;
58
60
  hasToolCalls: boolean;
59
61
  lastToolCallId?: string;
60
62
  };
@@ -16,5 +16,6 @@ export * from "./shared.js";
16
16
  export * from "./streaming-panel.js";
17
17
  export * from "./tool-activity.js";
18
18
  export * from "./transcript-container.js";
19
+ export * from "./transcript-notice.js";
19
20
  export * from "./transcript-status.js";
20
21
  export * from "./visual-truncate.js";
@@ -0,0 +1,35 @@
1
+ import type { TspText } from "@oh-my-pi/pi-wire";
2
+ import type { NativeNode } from "../native/node.js";
3
+ import type { Component } from "../tui.js";
4
+ import { TranscriptBlock } from "./transcript-container.js";
5
+ /** How loud an inline transcript notice is; a native terminal styles it as `omp.notice.<tone>`. */
6
+ export type NoticeTone = "info" | "success" | "warning" | "error";
7
+ /** Optional parts of a {@link noticeRow}. */
8
+ export interface NoticeRowOptions {
9
+ /** Icon name in place of the tone's own (`read` for a file read, `download` for an update). */
10
+ readonly icon?: string;
11
+ readonly key?: string;
12
+ }
13
+ /**
14
+ * One inline transcript notice for a native terminal (`omp.notice.<tone>`): a
15
+ * leading icon beside the wrapped message. Warnings, errors, background-job
16
+ * completions and prompt file reads all draw as this row, so they read alike.
17
+ * The tone colors the icon (an info notice keeps it quiet); the message spans
18
+ * carry emphasis only (`path`, `mono`, `dim`), never the tone itself.
19
+ */
20
+ export declare function noticeRow(tone: NoticeTone, message: TspText, options?: NoticeRowOptions): NativeNode;
21
+ /**
22
+ * A transcript block that a native terminal draws as one {@link noticeRow}
23
+ * while a text terminal draws `ansi`.
24
+ */
25
+ export declare class TranscriptNotice extends TranscriptBlock {
26
+ #private;
27
+ constructor(native: NativeNode, ansi: readonly Component[]);
28
+ /**
29
+ * `showWarning` / `showError`: a blank row and the `Warning: …` / `Error: …`
30
+ * line in its color in a text terminal; natively the tone's icon and the
31
+ * message, the label left to the icon.
32
+ */
33
+ static line(tone: "warning" | "error", message: string): TranscriptNotice;
34
+ describe(): NativeNode;
35
+ }