@linxiraos/pi-tui 1.1.27 → 1.2.0

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 (373) hide show
  1. package/CHANGELOG.md +47 -463
  2. package/README.md +7 -5
  3. package/dist/types/app-keybindings.d.ts +1 -1
  4. package/dist/types/apps/cleanse-board.d.ts +1 -1
  5. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  6. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  7. package/dist/types/apps/git/git-tui.d.ts +2 -2
  8. package/dist/types/apps/git/help.d.ts +3 -1
  9. package/dist/types/apps/if-bench-board.d.ts +2 -2
  10. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  11. package/dist/types/autocomplete.d.ts +5 -2
  12. package/dist/types/charts/chart-plan.d.ts +674 -0
  13. package/dist/types/charts/chart-svg.d.ts +59 -0
  14. package/dist/types/charts/table-data.d.ts +175 -0
  15. package/dist/types/chat/assistant-message.d.ts +5 -0
  16. package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
  17. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  18. package/dist/types/chat/execution-shared.d.ts +6 -5
  19. package/dist/types/chat/fence-figure.d.ts +42 -0
  20. package/dist/types/chat/figure-markdown.d.ts +42 -0
  21. package/dist/types/chat/messages.d.ts +1 -1
  22. package/dist/types/chat/svg-figure.d.ts +33 -0
  23. package/dist/types/chat/svg-source.d.ts +49 -0
  24. package/dist/types/chat/table-chart.d.ts +89 -0
  25. package/dist/types/chat/tool-execution.d.ts +2 -0
  26. package/dist/types/chat/user-message.d.ts +15 -2
  27. package/dist/types/chrome/shared.d.ts +9 -0
  28. package/dist/types/chrome/transcript-container.d.ts +6 -0
  29. package/dist/types/components/box.d.ts +1 -0
  30. package/dist/types/components/disclosure.d.ts +1 -0
  31. package/dist/types/components/editor.d.ts +15 -4
  32. package/dist/types/components/form.d.ts +1 -0
  33. package/dist/types/components/image.d.ts +12 -44
  34. package/dist/types/components/key-value-list.d.ts +1 -0
  35. package/dist/types/components/layout/row.d.ts +1 -0
  36. package/dist/types/components/layout/stack.d.ts +1 -0
  37. package/dist/types/components/markdown.d.ts +36 -3
  38. package/dist/types/components/scroll-view.d.ts +1 -0
  39. package/dist/types/components/section.d.ts +1 -0
  40. package/dist/types/components/table.d.ts +1 -0
  41. package/dist/types/components/text.d.ts +1 -0
  42. package/dist/types/components/tree-view.d.ts +7 -0
  43. package/dist/types/components/truncated-text.d.ts +1 -0
  44. package/dist/types/fuzzy.d.ts +29 -1
  45. package/dist/types/key-hint-format.d.ts +1 -1
  46. package/dist/types/keybinding-matchers.d.ts +3 -0
  47. package/dist/types/keybindings.d.ts +1 -1
  48. package/dist/types/kitty-graphics.d.ts +2 -2
  49. package/dist/types/loop-watchdog.d.ts +8 -7
  50. package/dist/types/multiplexers/cmux.d.ts +16 -0
  51. package/dist/types/multiplexers/herdr.d.ts +26 -0
  52. package/dist/types/multiplexers/notify.d.ts +13 -0
  53. package/dist/types/multiplexers/orca.d.ts +12 -0
  54. package/dist/types/multiplexers/screen.d.ts +9 -0
  55. package/dist/types/multiplexers/tmux.d.ts +29 -0
  56. package/dist/types/multiplexers/types.d.ts +100 -0
  57. package/dist/types/multiplexers/wmux.d.ts +12 -0
  58. package/dist/types/multiplexers/zellij.d.ts +12 -0
  59. package/dist/types/native/backend.d.ts +3 -2
  60. package/dist/types/native/blobs.d.ts +7 -3
  61. package/dist/types/native/encode.d.ts +3 -4
  62. package/dist/types/native/overlay.d.ts +5 -0
  63. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  64. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  65. package/dist/types/overlays/agent-hub.d.ts +2 -0
  66. package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -2
  67. package/dist/types/overlays/agents-hub.d.ts +9 -0
  68. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  69. package/dist/types/overlays/annotation-types.d.ts +2 -0
  70. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  71. package/dist/types/overlays/history-search.d.ts +10 -0
  72. package/dist/types/overlays/hook-selector.d.ts +9 -4
  73. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  74. package/dist/types/overlays/model-browser.d.ts +54 -7
  75. package/dist/types/overlays/model-hub.d.ts +23 -0
  76. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  77. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  78. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  79. package/dist/types/overlays/session-selector.d.ts +6 -2
  80. package/dist/types/overlays/settings-defs.d.ts +1 -0
  81. package/dist/types/overlays/usage-dashboard.d.ts +8 -4
  82. package/dist/types/overlays/usage-display.d.ts +3 -1
  83. package/dist/types/prompt/composer.d.ts +1 -1
  84. package/dist/types/prompt/custom-editor.d.ts +11 -8
  85. package/dist/types/prompt/image-source.d.ts +2 -1
  86. package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
  87. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  88. package/dist/types/prompt/welcome.d.ts +6 -1
  89. package/dist/types/render/composite.d.ts +2 -0
  90. package/dist/types/render/output-pane.d.ts +1 -0
  91. package/dist/types/render/render-utils.d.ts +1 -0
  92. package/dist/types/render/tool-card.d.ts +6 -0
  93. package/dist/types/render/width-aware-text.d.ts +2 -0
  94. package/dist/types/setup/scenes/types.d.ts +1 -0
  95. package/dist/types/status-line/component.d.ts +3 -0
  96. package/dist/types/status-line/host.d.ts +4 -1
  97. package/dist/types/status-line/segments.d.ts +2 -0
  98. package/dist/types/status-line/types.d.ts +14 -3
  99. package/dist/types/terminal-capabilities.d.ts +27 -35
  100. package/dist/types/terminal-multiplexer.d.ts +127 -14
  101. package/dist/types/terminal.d.ts +22 -6
  102. package/dist/types/theme/active-symbols.d.ts +1 -1
  103. package/dist/types/theme/schema.d.ts +11 -2
  104. package/dist/types/theme/symbols.d.ts +1 -1
  105. package/dist/types/theme/theme-class.d.ts +7 -0
  106. package/dist/types/theme/theme.d.ts +1 -1
  107. package/dist/types/theme/tui-adapters.d.ts +3 -0
  108. package/dist/types/tmux.d.ts +11 -4
  109. package/dist/types/tools/daemon.d.ts +1 -1
  110. package/dist/types/tools/eval.d.ts +22 -3
  111. package/dist/types/tools/glob.d.ts +4 -0
  112. package/dist/types/tools/native-view.d.ts +47 -0
  113. package/dist/types/tools/output-meta.d.ts +11 -1
  114. package/dist/types/tools/renderer.d.ts +37 -2
  115. package/dist/types/tools/streaming-output.d.ts +20 -1
  116. package/dist/types/tools/task.d.ts +0 -4
  117. package/dist/types/tools/write.d.ts +9 -2
  118. package/dist/types/tools/yield.d.ts +22 -0
  119. package/dist/types/tui.d.ts +31 -0
  120. package/dist/types/utils.d.ts +6 -0
  121. package/dist/types/vim.d.ts +19 -5
  122. package/package.json +13 -9
  123. package/src/app-keybindings.d.ts +1 -1
  124. package/src/app-keybindings.ts +1 -1
  125. package/src/apps/autoresearch-dashboard.ts +7 -3
  126. package/src/apps/cleanse-board.ts +2 -2
  127. package/src/apps/cleanse-picker.ts +1 -1
  128. package/src/apps/debug/log-viewer.ts +188 -72
  129. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  130. package/src/apps/debug/raw-sse.ts +39 -5
  131. package/src/apps/git/diff-pane.ts +26 -5
  132. package/src/apps/git/git-tui.ts +2 -2
  133. package/src/apps/git/help.ts +29 -20
  134. package/src/apps/if-bench-board.ts +2 -2
  135. package/src/apps/ps-top.ts +2 -2
  136. package/src/apps/setup-model-picker.ts +1 -1
  137. package/src/autocomplete.d.ts +5 -2
  138. package/src/autocomplete.ts +67 -43
  139. package/src/charts/chart-plan.d.ts +674 -0
  140. package/src/charts/chart-plan.ts +5081 -0
  141. package/src/charts/chart-svg.d.ts +59 -0
  142. package/src/charts/chart-svg.ts +4657 -0
  143. package/src/charts/table-data.d.ts +175 -0
  144. package/src/charts/table-data.ts +1478 -0
  145. package/src/chat/advisor-message.ts +11 -0
  146. package/src/chat/assistant-message.d.ts +5 -0
  147. package/src/chat/assistant-message.ts +219 -50
  148. package/src/chat/chat-transcript-builder.d.ts +2 -0
  149. package/src/chat/chat-transcript-builder.ts +5 -3
  150. package/src/chat/compaction-summary-message.d.ts +1 -0
  151. package/src/chat/compaction-summary-message.ts +8 -0
  152. package/src/chat/execution-shared.d.ts +6 -5
  153. package/src/chat/execution-shared.ts +42 -25
  154. package/src/chat/fence-figure.d.ts +42 -0
  155. package/src/chat/fence-figure.ts +80 -0
  156. package/src/chat/figure-markdown.d.ts +42 -0
  157. package/src/chat/figure-markdown.ts +174 -0
  158. package/src/chat/messages.d.ts +1 -1
  159. package/src/chat/messages.ts +1 -1
  160. package/src/chat/read-tool-group.ts +6 -4
  161. package/src/chat/skill-message.ts +6 -0
  162. package/src/chat/svg-figure.d.ts +33 -0
  163. package/src/chat/svg-figure.ts +399 -0
  164. package/src/chat/svg-source.d.ts +49 -0
  165. package/src/chat/svg-source.ts +188 -0
  166. package/src/chat/table-chart.d.ts +89 -0
  167. package/src/chat/table-chart.ts +294 -0
  168. package/src/chat/tool-execution.d.ts +2 -0
  169. package/src/chat/tool-execution.ts +80 -6
  170. package/src/chat/user-message.d.ts +15 -2
  171. package/src/chat/user-message.ts +55 -3
  172. package/src/chrome/shared.ts +53 -0
  173. package/src/chrome/transcript-container.d.ts +6 -0
  174. package/src/chrome/transcript-container.ts +93 -13
  175. package/src/components/box.d.ts +1 -0
  176. package/src/components/box.ts +5 -0
  177. package/src/components/disclosure.d.ts +1 -0
  178. package/src/components/disclosure.ts +8 -0
  179. package/src/components/editor.d.ts +15 -4
  180. package/src/components/editor.ts +294 -46
  181. package/src/components/form.d.ts +1 -0
  182. package/src/components/form.ts +8 -0
  183. package/src/components/image.d.ts +12 -44
  184. package/src/components/image.ts +47 -151
  185. package/src/components/key-value-list.d.ts +1 -0
  186. package/src/components/key-value-list.ts +4 -0
  187. package/src/components/layout/row.d.ts +1 -0
  188. package/src/components/layout/row.ts +7 -0
  189. package/src/components/layout/stack.d.ts +1 -0
  190. package/src/components/layout/stack.ts +7 -0
  191. package/src/components/markdown.d.ts +36 -3
  192. package/src/components/markdown.ts +433 -318
  193. package/src/components/scroll-view.d.ts +1 -0
  194. package/src/components/scroll-view.ts +5 -0
  195. package/src/components/section.d.ts +1 -0
  196. package/src/components/section.ts +5 -0
  197. package/src/components/settings-list.ts +2 -0
  198. package/src/components/table.d.ts +1 -0
  199. package/src/components/table.ts +4 -0
  200. package/src/components/text.d.ts +1 -0
  201. package/src/components/text.ts +7 -0
  202. package/src/components/tree-view.d.ts +7 -0
  203. package/src/components/tree-view.ts +38 -8
  204. package/src/components/truncated-text.d.ts +1 -0
  205. package/src/components/truncated-text.ts +5 -0
  206. package/src/desktop-notify.ts +1 -1
  207. package/src/fuzzy.d.ts +29 -1
  208. package/src/fuzzy.ts +115 -30
  209. package/src/key-hint-format.d.ts +1 -1
  210. package/src/key-hint-format.ts +1 -1
  211. package/src/keybinding-matchers.d.ts +3 -0
  212. package/src/keybinding-matchers.ts +6 -2
  213. package/src/keybindings.d.ts +1 -1
  214. package/src/keybindings.ts +1 -1
  215. package/src/kitty-graphics.d.ts +2 -2
  216. package/src/kitty-graphics.ts +4 -4
  217. package/src/loop-watchdog.d.ts +8 -7
  218. package/src/loop-watchdog.ts +18 -13
  219. package/src/multiplexers/cmux.d.ts +16 -0
  220. package/src/multiplexers/cmux.ts +29 -0
  221. package/src/multiplexers/herdr.d.ts +26 -0
  222. package/src/multiplexers/herdr.ts +64 -0
  223. package/src/multiplexers/notify.d.ts +13 -0
  224. package/src/multiplexers/notify.ts +24 -0
  225. package/src/multiplexers/orca.d.ts +12 -0
  226. package/src/multiplexers/orca.ts +16 -0
  227. package/src/multiplexers/screen.d.ts +9 -0
  228. package/src/multiplexers/screen.ts +15 -0
  229. package/src/multiplexers/tmux.d.ts +29 -0
  230. package/src/multiplexers/tmux.ts +40 -0
  231. package/src/multiplexers/types.d.ts +100 -0
  232. package/src/multiplexers/types.ts +110 -0
  233. package/src/multiplexers/wmux.d.ts +12 -0
  234. package/src/multiplexers/wmux.ts +16 -0
  235. package/src/multiplexers/zellij.d.ts +12 -0
  236. package/src/multiplexers/zellij.ts +24 -0
  237. package/src/native/backend.d.ts +3 -2
  238. package/src/native/backend.ts +174 -18
  239. package/src/native/blobs.d.ts +7 -3
  240. package/src/native/blobs.ts +47 -12
  241. package/src/native/encode.d.ts +3 -4
  242. package/src/native/encode.ts +58 -31
  243. package/src/native/overlay.d.ts +5 -0
  244. package/src/native/overlay.ts +13 -0
  245. package/src/native/reconcile.ts +4 -0
  246. package/src/overlays/agent-hub-projection.d.ts +6 -5
  247. package/src/overlays/agent-hub-projection.ts +124 -13
  248. package/src/overlays/agent-hub-types.d.ts +9 -0
  249. package/src/overlays/agent-hub-types.ts +6 -0
  250. package/src/overlays/agent-hub.ts +44 -29
  251. package/src/overlays/agent-transcript-viewer.ts +91 -23
  252. package/src/overlays/agents-hub.ts +28 -8
  253. package/src/overlays/annotation-overlay.ts +293 -90
  254. package/src/overlays/annotation-types.ts +2 -0
  255. package/src/overlays/ask-dialog.ts +7 -10
  256. package/src/overlays/btw-history-panel.ts +12 -1
  257. package/src/overlays/copy-selector.ts +30 -8
  258. package/src/overlays/extensions/state-manager.ts +52 -12
  259. package/src/overlays/history-search.ts +100 -7
  260. package/src/overlays/hook-selector.ts +12 -5
  261. package/src/overlays/jobs-panel.ts +28 -46
  262. package/src/overlays/model-browser.ts +341 -110
  263. package/src/overlays/model-hub.ts +300 -49
  264. package/src/overlays/move-overlay.ts +28 -14
  265. package/src/overlays/oauth-selector.ts +54 -70
  266. package/src/overlays/plan-review-overlay.ts +194 -70
  267. package/src/overlays/plugin-settings.ts +3 -3
  268. package/src/overlays/report-panel.ts +2 -2
  269. package/src/overlays/rewind-selector.ts +86 -17
  270. package/src/overlays/session-observer-registry.d.ts +2 -0
  271. package/src/overlays/session-observer-registry.ts +10 -2
  272. package/src/overlays/session-selector.ts +64 -26
  273. package/src/overlays/settings-defs.ts +4 -3
  274. package/src/overlays/settings-selector.ts +33 -8
  275. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  276. package/src/overlays/stats-notice.ts +1 -1
  277. package/src/overlays/tree-selector.ts +41 -20
  278. package/src/overlays/usage-dashboard.ts +100 -50
  279. package/src/overlays/usage-display.ts +10 -4
  280. package/src/prompt/attachment-chips.ts +15 -11
  281. package/src/prompt/composer-attachments.ts +6 -4
  282. package/src/prompt/composer.ts +4 -15
  283. package/src/prompt/custom-editor.ts +54 -26
  284. package/src/prompt/image-source.ts +2 -1
  285. package/src/prompt/interactive-context-helpers.ts +30 -18
  286. package/src/prompt/macos-spelling.ts +7 -0
  287. package/src/prompt/magic-keywords.ts +11 -2
  288. package/src/prompt/model-mention-autocomplete.ts +14 -14
  289. package/src/prompt/tips.txt +6 -5
  290. package/src/prompt/welcome.ts +55 -12
  291. package/src/prompt/word-completion.ts +38 -5
  292. package/src/render/composite.d.ts +2 -0
  293. package/src/render/composite.ts +64 -0
  294. package/src/render/output-pane.d.ts +1 -0
  295. package/src/render/output-pane.ts +6 -0
  296. package/src/render/render-utils.d.ts +1 -0
  297. package/src/render/render-utils.ts +11 -0
  298. package/src/render/tool-card.d.ts +6 -0
  299. package/src/render/tool-card.ts +15 -0
  300. package/src/render/width-aware-text.d.ts +2 -0
  301. package/src/render/width-aware-text.ts +5 -0
  302. package/src/setup/scenes/model.ts +2 -1
  303. package/src/setup/scenes/types.ts +1 -0
  304. package/src/status-line/component.ts +292 -78
  305. package/src/status-line/host.ts +4 -1
  306. package/src/status-line/segments.ts +40 -22
  307. package/src/status-line/types.ts +15 -3
  308. package/src/terminal-capabilities.d.ts +27 -35
  309. package/src/terminal-capabilities.ts +75 -189
  310. package/src/terminal-multiplexer.d.ts +127 -14
  311. package/src/terminal-multiplexer.ts +112 -33
  312. package/src/terminal.d.ts +22 -6
  313. package/src/terminal.ts +139 -37
  314. package/src/theme/active-symbols.d.ts +1 -1
  315. package/src/theme/active-symbols.ts +1 -1
  316. package/src/theme/schema-validation.ts +1 -0
  317. package/src/theme/schema.d.ts +11 -2
  318. package/src/theme/schema.ts +14 -1
  319. package/src/theme/session-color.ts +1 -7
  320. package/src/theme/symbols.d.ts +1 -1
  321. package/src/theme/symbols.ts +6 -1
  322. package/src/theme/theme-class.d.ts +7 -0
  323. package/src/theme/theme-class.ts +26 -1
  324. package/src/theme/theme-schema.json +5 -1
  325. package/src/theme/theme.d.ts +1 -1
  326. package/src/theme/theme.ts +8 -4
  327. package/src/theme/tui-adapters.d.ts +3 -0
  328. package/src/theme/tui-adapters.ts +68 -0
  329. package/src/tmux.d.ts +11 -4
  330. package/src/tmux.ts +47 -11
  331. package/src/tools/ast-edit.ts +59 -35
  332. package/src/tools/ast-grep.ts +35 -16
  333. package/src/tools/bash.ts +92 -42
  334. package/src/tools/daemon.d.ts +1 -1
  335. package/src/tools/daemon.ts +1 -1
  336. package/src/tools/default-renderer.ts +35 -19
  337. package/src/tools/edit.ts +142 -120
  338. package/src/tools/eval-format/index.ts +28 -2
  339. package/src/tools/eval-format/javascript.ts +7 -5
  340. package/src/tools/eval.d.ts +22 -3
  341. package/src/tools/eval.ts +364 -208
  342. package/src/tools/fetch.ts +5 -6
  343. package/src/tools/glob.d.ts +4 -0
  344. package/src/tools/glob.ts +33 -65
  345. package/src/tools/grep.ts +81 -48
  346. package/src/tools/index.ts +2 -0
  347. package/src/tools/lsp.ts +81 -59
  348. package/src/tools/mcp.ts +111 -68
  349. package/src/tools/native-view.d.ts +47 -0
  350. package/src/tools/native-view.ts +102 -1
  351. package/src/tools/output-meta.d.ts +11 -1
  352. package/src/tools/output-meta.ts +23 -3
  353. package/src/tools/read.ts +7 -5
  354. package/src/tools/renderer.d.ts +37 -2
  355. package/src/tools/renderer.ts +42 -2
  356. package/src/tools/streaming-output.d.ts +20 -1
  357. package/src/tools/streaming-output.ts +149 -50
  358. package/src/tools/task.d.ts +0 -4
  359. package/src/tools/task.ts +112 -59
  360. package/src/tools/todo.ts +119 -30
  361. package/src/tools/wait.ts +34 -24
  362. package/src/tools/web-search.ts +1 -1
  363. package/src/tools/write.d.ts +9 -2
  364. package/src/tools/write.ts +147 -95
  365. package/src/tools/yield.d.ts +22 -0
  366. package/src/tools/yield.ts +225 -0
  367. package/src/tui.d.ts +31 -0
  368. package/src/tui.ts +309 -160
  369. package/src/utils.d.ts +6 -0
  370. package/src/utils.ts +19 -0
  371. package/src/vim.d.ts +19 -5
  372. package/src/vim.ts +333 -18
  373. package/src/windows-input-mode.ts +8 -0
@@ -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
  },
@@ -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
@@ -86,6 +87,8 @@ export declare class AssistantMessageComponent extends Container {
86
87
  */
87
88
  setExpanded(expanded: boolean): void;
88
89
  isTranscriptBlockFinalized(): boolean;
90
+ /** Whether a figure still waits for the raster of its final source; the transcript holds retirement meanwhile. */
91
+ isTranscriptBlockPending(): boolean;
89
92
  render(width: number): readonly string[];
90
93
  /**
91
94
  * A `col` (role `zeta.assistant`) of `md` nodes keyed by content index, so
@@ -123,6 +126,8 @@ export declare class AssistantMessageComponent extends Container {
123
126
  messagePersistenceKey(): string | undefined;
124
127
  /** Toggle rendering for assistant-native and tool-result images. */
125
128
  setImagesVisible(visible: boolean): void;
129
+ /** Toggle charts under numeric tables (the main session's transcript only, never a subagent's). */
130
+ setTableChartsVisible(visible: boolean): void;
126
131
  /** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
127
132
  setToolResultImagesVisible(visible: boolean): void;
128
133
  setToolResultImages(toolCallId: string, images: ImageContent[]): void;
@@ -2,14 +2,19 @@ import type { AssistantMessage, ImageContent, TextContent } from "@linxiraos/pi-
2
2
  import { type Component, Container } from "../tui";
3
3
  import { Image, type ImageBudget } from "../components/image";
4
4
  import { ImageProtocol, TERMINAL } from "../terminal-capabilities";
5
- import { Markdown, type MarkdownTheme } from "../components/markdown";
5
+ import {
6
+ type DefaultTextStyle,
7
+ Markdown,
8
+ type MarkdownTheme,
9
+ rewriteMarkdownLinkDestinations,
10
+ } from "../components/markdown";
6
11
  import { Spacer } from "../components/spacer";
7
12
  import { Text } from "../components/text";
8
13
  import { formatDuration, formatNumber } from "@linxiraos/pi-utils";
9
14
  import chalk from "@linxiraos/pi-utils/chalk";
10
15
  import { LRUCache } from "@linxiraos/pi-utils/lru";
11
16
  import type { AssistantThinkingRenderer } from "./extension-types";
12
- import { ensureThemeSync, getMarkdownTheme, getThemeEpoch, theme } from "../theme";
17
+ import { ensureThemeSync, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getThemeEpoch, theme } from "../theme";
13
18
  import { card, col, elapsed, node, span, text } from "../native/describe";
14
19
  import { hasTranscriptActions, runTranscriptAction } from "./transcript-actions";
15
20
  import type { NativeChild, NativeNode, NativeUiEvent } from "../native/node";
@@ -27,6 +32,10 @@ import { type ServedModelMismatch, ServedModelMarkerComponent } from "./served-m
27
32
  import { isReactionTarget, type ReactionSplit, type ReactionTarget, splitReaction } from "./reaction";
28
33
  import { isRowPrefix, type TranscriptStableRow, trimBlankEdges } from "../chrome/transcript-container";
29
34
  import { formatTurnUsage, type TurnUsageSummary } from "../overlays/usage-row";
35
+ import { FigureMarkdown } from "./figure-markdown";
36
+ import { svgFigureRendering } from "./svg-figure";
37
+ import { hasSvgFence } from "./svg-source";
38
+ import { describeTableChart, hasChartTable, lookupTableChart, splitTableCharts } from "./table-chart";
30
39
 
31
40
  /**
32
41
  * Max wrapped rows of a turn-ending provider error rendered inline in the
@@ -46,6 +55,15 @@ function thoughtLabel(clock: { start: number; end?: number } | undefined): strin
46
55
  }
47
56
 
48
57
  type ThinkingContentBlock = Extract<AssistantMessage["content"][number], { type: "thinking" }>;
58
+ /** Renders one text or thinking block: Markdown, or {@link FigureMarkdown} for text holding a ```svg fence. */
59
+ type ProseBlock = Markdown | FigureMarkdown;
60
+ /** A streamed block the fast path updates in place. */
61
+ interface FastPathItem {
62
+ md: ProseBlock;
63
+ contentIndex: number;
64
+ blockType: "text" | "thinking";
65
+ lastText: string;
66
+ }
49
67
  type DisplayThinkingContentBlock = ThinkingContentBlock & { rawThinking?: string };
50
68
  type StablePartKind = "thinking" | "text";
51
69
  type StablePart = { kind: StablePartKind; text: string } | { kind: "spacer" };
@@ -230,22 +248,30 @@ export class AssistantMessageComponent extends Container {
230
248
  #cacheMarker?: CacheInvalidationMarkerComponent;
231
249
  #servedModelMarker?: ServedModelMarkerComponent;
232
250
  #lastMessage?: AssistantMessage;
233
- #emergencyText?: Markdown;
251
+ #emergencyText?: ProseBlock;
234
252
  #toolImagesByCallId = new Map<string, ImageContent[]>();
235
253
  /**
236
254
  * Payload keys ({@link imagePayloadKey}) whose Kitty PNG conversion this
237
- * component already awaits, so a re-delivered image neither re-encodes nor
255
+ * component is awaiting, so a re-delivered image neither re-encodes nor
238
256
  * schedules a second {@link updateContent} cascade. The conversions
239
- * themselves are shared process-wide by {@link convertImageToPngShared}.
257
+ * themselves live in the bounded process-wide cache behind
258
+ * {@link convertImageToPngShared}; an evicted one is redone on the next
259
+ * render instead of being pinned here for the session.
240
260
  */
241
261
  #kittyConversionsAwaited = new Set<string>();
242
262
  /**
243
- * Conversions this component displays, held so a later re-render (theme
244
- * invalidation) still finds them after the bounded shared cache evicts them.
263
+ * Conversions the current image children display, by payload key. Rebuilt
264
+ * on every full render pass ({@link updateContent}), so a conversion is held
265
+ * only while it is on screen — a re-render after the shared cache evicted
266
+ * it still finds it — and released once hidden or replaced.
245
267
  */
246
- #kittyConverted = new Map<string, ImageContent>();
268
+ #kittyDisplayed = new Map<string, ImageContent>();
269
+ /** The previous pass's {@link #kittyDisplayed}, readable only during a render pass. */
270
+ #kittyPreviouslyDisplayed: Map<string, ImageContent> | undefined;
247
271
  #showImages = true;
248
272
  #showToolResultImages = true;
273
+ /** Charts under numeric tables; off for subagent transcripts. */
274
+ #showTableCharts = true;
249
275
  #transcriptBlockFinalized: boolean;
250
276
  /** See {@link setMidStreamPublication}; the wire's `stream-revision` axis decides it. */
251
277
  #midStreamPublication = true;
@@ -293,9 +319,13 @@ export class AssistantMessageComponent extends Container {
293
319
  #lastUpdateTransient = false;
294
320
  // Fast-path state: reuse Markdown children when message shape is stable during streaming.
295
321
  #fastPathKey: string | undefined;
296
- #fastPathItems:
297
- | Array<{ md: Markdown; contentIndex: number; blockType: "text" | "thinking"; lastText: string }>
298
- | undefined;
322
+ #fastPathItems: FastPathItem[] | undefined;
323
+ /**
324
+ * Text blocks whose ```svg fences are lifted into figures, by content index.
325
+ * Kept across slow-path rebuilds so a figure keeps its raster (and its
326
+ * in-flight one) instead of starting over on every update.
327
+ */
328
+ #figureBlocks = new Map<number, FigureMarkdown>();
299
329
  /** Live "thinking" pulse shown in place of a hidden thinking block while it
300
330
  * streams; undefined when not animating. Driven by {@link #thinkingDotsTimer}. */
301
331
  #thinkingDots: Text | undefined;
@@ -339,6 +369,8 @@ export class AssistantMessageComponent extends Container {
339
369
  #textColorTransform?: (text: string) => string;
340
370
  #linkTargets: ReadonlyMap<string, string> = EMPTY_LINK_TARGETS;
341
371
  #markdownTheme: MarkdownTheme | undefined;
372
+ /** Text-block sources with {@link #linkTargets} applied, for the native `md` nodes; reset with the targets. */
373
+ #nativeLinkSources = new Map<string, string>();
342
374
  /** Block this reply reacts to; undefined when the preceding block takes no reactions. */
343
375
  #reactionTarget: ReactionTarget | undefined;
344
376
  /** Reaction lifted from the reply's opening emoji, once resolved. */
@@ -356,16 +388,38 @@ export class AssistantMessageComponent extends Container {
356
388
  /** Markdown nodes by key, reused while their text and streaming flag are unchanged. */
357
389
  #nativeParts = new Map<string, { text: string; stream: boolean; node: NativeNode }>();
358
390
  readonly #nativeImages = new NativeImageCache();
391
+ /** Smart table chart picks a TSP describe is waiting on. */
392
+ readonly #chartPicks = new Set<Promise<void>>();
359
393
 
360
394
  setTextColorTransform(transform?: (text: string) => string): void {
361
395
  this.#textColorTransform = transform;
362
396
  }
363
397
 
398
+ /**
399
+ * Default text style for assistant paragraph Markdown. An explicitly
400
+ * installed transform (live-command output) always wins and keeps its
401
+ * historical paint-everything contract; otherwise the optional
402
+ * `assistantMessageText` theme token paints prose, flagged `proseFg` so
403
+ * markdown sub-elements keep their own `md*` tokens (docs/theme.md).
404
+ * Returns undefined when the token is unset or empty so the terminal
405
+ * default foreground is used byte-for-byte, exactly as before the token
406
+ * existed.
407
+ *
408
+ * The token is re-derived on every rebuild (the theme-change path calls
409
+ * `invalidate()` and rebuilds through the teardown path, which re-consults
410
+ * the active theme), so prose follows theme switches both ways.
411
+ */
412
+ #getProseTextStyle(): DefaultTextStyle | undefined {
413
+ if (this.#textColorTransform) return { color: this.#textColorTransform };
414
+ if (typeof theme === "undefined" || !theme.hasColor("assistantMessageText")) return undefined;
415
+ return { color: (text: string) => theme.fg("assistantMessageText", text), proseFg: true };
416
+ }
417
+
364
418
  #getProseTheme(): MarkdownTheme {
365
419
  if (this.#markdownTheme) return this.#markdownTheme;
366
420
  const base = getMarkdownTheme();
367
421
  const snapshot = this.#linkTargets;
368
- const markdownTheme = snapshot.size > 0 ? { ...base, resolveLink: (href: string) => snapshot.get(href) } : base;
422
+ const markdownTheme = snapshot.size > 0 ? getMarkdownThemeWithLinkTargets(snapshot) : base;
369
423
  this.#markdownTheme = markdownTheme;
370
424
  return markdownTheme;
371
425
  }
@@ -387,6 +441,9 @@ export class AssistantMessageComponent extends Container {
387
441
  this.#markdownTheme = undefined;
388
442
  this.#fastPathKey = undefined;
389
443
  this.#fastPathItems = undefined;
444
+ for (const block of this.#figureBlocks.values()) block.restyle();
445
+ this.#nativeLinkSources.clear();
446
+ this.#nativeViewVersion++;
390
447
  if (this.#lastMessage) {
391
448
  this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
392
449
  }
@@ -537,11 +594,18 @@ export class AssistantMessageComponent extends Container {
537
594
  this.#markdownTheme = undefined;
538
595
  this.#fastPathKey = undefined;
539
596
  this.#fastPathItems = undefined;
597
+ for (const block of this.#figureBlocks.values()) block.restyle();
540
598
  if (this.#lastMessage) {
541
599
  this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
542
600
  }
543
601
  }
544
602
 
603
+ override releaseRenderCaches(): void {
604
+ super.releaseRenderCaches();
605
+ this.#dropStableRenders();
606
+ this.#emergencyText?.releaseRenderCaches();
607
+ }
608
+
545
609
  setHideThinkingBlock(hide: boolean): void {
546
610
  this.#hideThinkingBlock = hide;
547
611
  this.#thinkingCollapsed.clear();
@@ -713,6 +777,12 @@ export class AssistantMessageComponent extends Container {
713
777
  return this.#transcriptBlockFinalized;
714
778
  }
715
779
 
780
+ /** Whether a figure still waits for the raster of its final source; the transcript holds retirement meanwhile. */
781
+ isTranscriptBlockPending(): boolean {
782
+ for (const block of this.#figureBlocks.values()) if (block.pending) return true;
783
+ return false;
784
+ }
785
+
716
786
  override render(width: number): readonly string[] {
717
787
  const rows = super.render(width);
718
788
  this.#publishStableSnapshot(rows, width);
@@ -806,7 +876,36 @@ export class AssistantMessageComponent extends Container {
806
876
  const content = message.content[index]!;
807
877
  const streaming = live && index === tailIndex;
808
878
  if (content.type === "text" && canonicalizeMessage(content.text)) {
809
- children.push(markdown(`t${index}`, `t${index}`, content.text.trim(), streaming));
879
+ const source = content.text.trim();
880
+ // The terminal renders this source itself and would resolve a relative
881
+ // link against its own idea of the cwd: hand it the session-resolved
882
+ // targets (resolved once the segment closes, so never while streaming).
883
+ const linked = (text: string, live: boolean): string => {
884
+ if (live || this.#linkTargets.size === 0) return text;
885
+ const targets = this.#linkTargets;
886
+ const resolved =
887
+ this.#nativeLinkSources.get(text) ??
888
+ rewriteMarkdownLinkDestinations(text, href => targets.get(href));
889
+ this.#nativeLinkSources.set(text, resolved);
890
+ return resolved;
891
+ };
892
+ if (!this.#showImages || !this.#showTableCharts || !hasChartTable(source)) {
893
+ children.push(markdown(`t${index}`, `t${index}`, linked(source, streaming), streaming));
894
+ continue;
895
+ }
896
+ // A chart follows its table as an SVG image node; the prose splits around it.
897
+ const segments = splitTableCharts(source, streaming);
898
+ segments.forEach((segment, part) => {
899
+ const slot = part === 0 ? `t${index}` : `t${index}.${part}`;
900
+ if (segment.kind === "markdown") {
901
+ const live = streaming && part === segments.length - 1;
902
+ children.push(markdown(slot, slot, linked(segment.text.trim(), live), live));
903
+ return;
904
+ }
905
+ const chart = lookupTableChart(segment.table);
906
+ if (chart instanceof Promise) this.#awaitChart(chart);
907
+ else if (chart) children.push(describeTableChart(chart, slot));
908
+ });
810
909
  } else if (content.type === "thinking") {
811
910
  const display = resolveThinkingDisplay(content, this.#proseOnlyThinking);
812
911
  if (!display.visible) continue;
@@ -1089,12 +1188,18 @@ export class AssistantMessageComponent extends Container {
1089
1188
  for (const child of this.#contentContainer.children) {
1090
1189
  const item = items[itemIndex];
1091
1190
  if (item?.md === child) {
1191
+ const md = item.md;
1192
+ if (md instanceof FigureMarkdown) {
1193
+ // Plain Markdown reproduces only the prose ahead of the first figure.
1194
+ if (md.leadingProse) parts.push({ kind: item.blockType, text: md.leadingProse });
1195
+ break;
1196
+ }
1092
1197
  if (itemIndex === items.length - 1) {
1093
1198
  // Streaming child: publish Markdown's frozen prefix, and only
1094
1199
  // once non-blank content exists past it — the block's last
1095
1200
  // non-blank line is still being written (thinking's prose fold
1096
1201
  // may rewrite it) and must stay out of published bytes.
1097
- const raw = item.md.getLastRenderStableText();
1202
+ const raw = md.getLastRenderStableText();
1098
1203
  const frozen = raw.trim();
1099
1204
  if (frozen.length > 0 && /\S/.test(item.lastText.slice(raw.length))) {
1100
1205
  parts.push({ kind: item.blockType, text: frozen });
@@ -1122,7 +1227,7 @@ export class AssistantMessageComponent extends Container {
1122
1227
  /**
1123
1228
  * Render the first `partCount` stable parts, the final one cut to
1124
1229
  * `lastLength`. Rows match fresh Markdown renders of each part byte for
1125
- * byte — {@link #createStableMarkdown} mirrors the live children — but a
1230
+ * byte — {@link #createMarkdown} builds the live children too — but a
1126
1231
  * closed part renders once per width, and the growing final part reuses one
1127
1232
  * Markdown instance so its already-frozen blocks are not re-lexed.
1128
1233
  */
@@ -1172,7 +1277,7 @@ export class AssistantMessageComponent extends Container {
1172
1277
  // Trim like the live children, dropping the trailing blank line a frozen
1173
1278
  // prefix still carries.
1174
1279
  const trimmed = text.trim();
1175
- if (this.#transcriptBlockFinalized) return this.#createStableMarkdown(kind, trimmed).render(width);
1280
+ if (this.#transcriptBlockFinalized) return this.#createMarkdown(kind, trimmed).render(width);
1176
1281
  const head = this.#stableHeadRenderer;
1177
1282
  const replay = this.#stableReplayRenderer;
1178
1283
  const renderer = role === "head" ? head : role === "replay" ? replay : head?.index === index ? head : replay;
@@ -1180,29 +1285,74 @@ export class AssistantMessageComponent extends Container {
1180
1285
  renderer.md.setText(trimmed);
1181
1286
  return renderer.md.render(width);
1182
1287
  }
1183
- const md = this.#createStableMarkdown(kind, trimmed);
1288
+ const md = this.#createMarkdown(kind, trimmed);
1184
1289
  if (role === "head") this.#stableHeadRenderer = { index, kind, md };
1185
1290
  else if (role === "replay") this.#stableReplayRenderer = { index, kind, md };
1186
1291
  return md.render(width);
1187
1292
  }
1188
1293
 
1189
- /** Constructor args mirror the live child Markdown so stable rows prefix the block render. */
1190
- #createStableMarkdown(kind: StablePartKind, text: string): Markdown {
1294
+ /** Markdown for a text or thinking block; live children and stable-row renders share it so stable rows prefix the block render. */
1295
+ #createMarkdown(kind: StablePartKind, text: string): Markdown {
1191
1296
  return kind === "text"
1192
- ? new Markdown(
1193
- text,
1194
- 1,
1195
- 0,
1196
- this.#getProseTheme(),
1197
- this.#textColorTransform ? { color: this.#textColorTransform } : undefined,
1198
- 0,
1199
- )
1297
+ ? new Markdown(text, 1, 0, this.#getProseTheme(), this.#getProseTextStyle(), 0)
1200
1298
  : new Markdown(text, 1, 0, getMarkdownTheme(), {
1201
1299
  color: (value: string) => theme.fg("thinkingText", value),
1202
1300
  italic: true,
1203
1301
  });
1204
1302
  }
1205
1303
 
1304
+ /**
1305
+ * Whether `text` holds a ```svg fence this terminal draws as a figure, or a
1306
+ * table it draws a chart under. Native (TSP) terminals receive the fence
1307
+ * verbatim in the `md` node and draw it themselves, and get charts as image
1308
+ * nodes from {@link describe}; image-less terminals keep fences as code and
1309
+ * tables as tables.
1310
+ */
1311
+ #liftsFigures(text: string): boolean {
1312
+ return (
1313
+ this.#showImages &&
1314
+ TERMINAL.imageProtocol !== null &&
1315
+ !isNativeRendering() &&
1316
+ ((svgFigureRendering() && hasSvgFence(text)) || (this.#showTableCharts && hasChartTable(text)))
1317
+ );
1318
+ }
1319
+
1320
+ /** Re-describe once a smart table chart pick lands. */
1321
+ #awaitChart(pick: Promise<void>): void {
1322
+ if (this.#chartPicks.has(pick)) return;
1323
+ this.#chartPicks.add(pick);
1324
+ void pick.then(() => {
1325
+ this.#chartPicks.delete(pick);
1326
+ this.#blockVersion++;
1327
+ this.#onImageUpdate?.();
1328
+ });
1329
+ }
1330
+
1331
+ /**
1332
+ * The component for text block `index`: Markdown, or a {@link FigureMarkdown}
1333
+ * when it holds a ```svg fence to lift or a table to chart — taken over from
1334
+ * `previous` so its figures keep their rasters across rebuilds.
1335
+ */
1336
+ #proseBlock(index: number, text: string, previous: ReadonlyMap<number, FigureMarkdown>): ProseBlock {
1337
+ if (!this.#liftsFigures(text)) return this.#createMarkdown("text", text);
1338
+ let block = previous.get(index);
1339
+ if (block) {
1340
+ block.setText(text);
1341
+ } else {
1342
+ block = new FigureMarkdown(text, {
1343
+ markdown: value => this.#createMarkdown("text", value),
1344
+ charts: this.#showTableCharts,
1345
+ budget: this.#imageBudget,
1346
+ onChange: () => {
1347
+ this.#blockVersion++;
1348
+ this.#onImageUpdate?.();
1349
+ },
1350
+ });
1351
+ }
1352
+ this.#figureBlocks.set(index, block);
1353
+ return block;
1354
+ }
1355
+
1206
1356
  #stableLedger(width: number): StableRowLedger {
1207
1357
  let ledger = this.#stableLedgers.get(width);
1208
1358
  if (ledger === undefined) {
@@ -1322,6 +1472,18 @@ export class AssistantMessageComponent extends Container {
1322
1472
  }
1323
1473
  }
1324
1474
 
1475
+ /** Toggle charts under numeric tables (the main session's transcript only, never a subagent's). */
1476
+ setTableChartsVisible(visible: boolean): void {
1477
+ if (this.#showTableCharts === visible) return;
1478
+ this.#showTableCharts = visible;
1479
+ // Figure blocks bake the switch in; rebuild them rather than reuse.
1480
+ for (const block of this.#figureBlocks.values()) block.dispose();
1481
+ this.#figureBlocks.clear();
1482
+ if (this.#lastMessage) {
1483
+ this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
1484
+ }
1485
+ }
1486
+
1325
1487
  /** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
1326
1488
  setToolResultImagesVisible(visible: boolean): void {
1327
1489
  if (this.#showToolResultImages === visible) return;
@@ -1345,22 +1507,24 @@ export class AssistantMessageComponent extends Container {
1345
1507
  }
1346
1508
  }
1347
1509
 
1510
+ /** A displayable Kitty PNG for a non-PNG `image`: the shared cache, else this component's displayed copy. */
1511
+ #kittyConversion(image: ImageContent): ImageContent | undefined {
1512
+ const cached = cachedPngConversion(image);
1513
+ if (cached) return cached;
1514
+ const key = imagePayloadKey(image);
1515
+ return this.#kittyDisplayed.get(key) ?? this.#kittyPreviouslyDisplayed?.get(key);
1516
+ }
1517
+
1348
1518
  #convertImagesForKitty(entries: Array<{ image: ImageContent; key: string }>): void {
1349
1519
  if (TERMINAL.imageProtocol !== ImageProtocol.Kitty) return;
1350
1520
  for (const { image } of entries) {
1351
- if (image.mimeType === "image/png") continue;
1521
+ if (image.mimeType === "image/png" || this.#kittyConversion(image)) continue;
1352
1522
  const key = imagePayloadKey(image);
1353
- if (this.#kittyConverted.has(key)) continue;
1354
- const cached = cachedPngConversion(image);
1355
- if (cached) {
1356
- this.#kittyConverted.set(key, cached);
1357
- continue;
1358
- }
1359
1523
  if (this.#kittyConversionsAwaited.has(key)) continue;
1360
1524
  this.#kittyConversionsAwaited.add(key);
1361
1525
  convertImageToPngShared(image)
1362
- .then(converted => {
1363
- this.#kittyConverted.set(key, converted);
1526
+ .then(() => {
1527
+ this.#kittyConversionsAwaited.delete(key);
1364
1528
  if (this.#lastMessage) {
1365
1529
  this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
1366
1530
  }
@@ -1378,10 +1542,11 @@ export class AssistantMessageComponent extends Container {
1378
1542
 
1379
1543
  if (withLeadingSpacer) this.#contentContainer.addChild(new Spacer(1));
1380
1544
  for (const { image, key } of entries) {
1381
- const displayImage =
1382
- TERMINAL.imageProtocol === ImageProtocol.Kitty && image.mimeType !== "image/png"
1383
- ? this.#kittyConverted.get(imagePayloadKey(image))
1384
- : image;
1545
+ let displayImage: ImageContent | undefined = image;
1546
+ if (TERMINAL.imageProtocol === ImageProtocol.Kitty && image.mimeType !== "image/png") {
1547
+ displayImage = this.#kittyConversion(image);
1548
+ if (displayImage) this.#kittyDisplayed.set(imagePayloadKey(image), displayImage);
1549
+ }
1385
1550
  if (TERMINAL.imageProtocol && displayImage) {
1386
1551
  this.#contentContainer.addChild(
1387
1552
  new Image(
@@ -1440,7 +1605,9 @@ export class AssistantMessageComponent extends Container {
1440
1605
  ];
1441
1606
  for (const content of message.content) {
1442
1607
  if (content.type === "text") {
1443
- parts.push(canonicalizeMessage(content.text) ? "T1" : "T0");
1608
+ parts.push(
1609
+ !canonicalizeMessage(content.text) ? "T0" : this.#liftsFigures(content.text.trim()) ? "TF" : "T1",
1610
+ );
1444
1611
  } else if (content.type === "thinking") {
1445
1612
  if (this.#hideThinkingBlock) {
1446
1613
  // Match the pulse's empty/nonempty transition without formatting hidden text.
@@ -1594,6 +1761,8 @@ export class AssistantMessageComponent extends Container {
1594
1761
 
1595
1762
  // Clear content container
1596
1763
  this.#contentContainer.clear();
1764
+ this.#kittyPreviouslyDisplayed = this.#kittyDisplayed;
1765
+ this.#kittyDisplayed = new Map();
1597
1766
  this.#thinkingExtensions.clear();
1598
1767
  this.#emergencyText = undefined;
1599
1768
  this.#thinkingDots = undefined;
@@ -1601,9 +1770,9 @@ export class AssistantMessageComponent extends Container {
1601
1770
 
1602
1771
  // Determine if we should capture Markdown instances for next fast path
1603
1772
  const shouldCapture = this.#canFastPath(message);
1604
- const captureItems:
1605
- | Array<{ md: Markdown; contentIndex: number; blockType: "text" | "thinking"; lastText: string }>
1606
- | undefined = shouldCapture ? [] : undefined;
1773
+ const captureItems: FastPathItem[] | undefined = shouldCapture ? [] : undefined;
1774
+ const previousFigures = this.#figureBlocks;
1775
+ this.#figureBlocks = new Map();
1607
1776
 
1608
1777
  const hasVisibleContent = message.content.some(
1609
1778
  c =>
@@ -1622,8 +1791,7 @@ export class AssistantMessageComponent extends Container {
1622
1791
  if (content.type === "text" && canonicalizeMessage(content.text)) {
1623
1792
  // Set paddingY=0 to avoid extra spacing before tool executions
1624
1793
  const trimmed = content.text.trim();
1625
- const mdOptions = this.#textColorTransform ? { color: this.#textColorTransform } : undefined;
1626
- const md = new Markdown(trimmed, 1, 0, this.#getProseTheme(), mdOptions, 0);
1794
+ const md = this.#proseBlock(i, trimmed, previousFigures);
1627
1795
  this.#contentContainer.addChild(md);
1628
1796
  this.#emergencyText = md;
1629
1797
  captureItems?.push({ md, contentIndex: i, blockType: "text", lastText: trimmed });
@@ -1648,10 +1816,7 @@ export class AssistantMessageComponent extends Container {
1648
1816
  );
1649
1817
 
1650
1818
  // Thinking traces in thinkingText color, italic
1651
- const md = new Markdown(thinkingText, 1, 0, getMarkdownTheme(), {
1652
- color: (text: string) => theme.fg("thinkingText", text),
1653
- italic: true,
1654
- });
1819
+ const md = this.#createMarkdown("thinking", thinkingText);
1655
1820
  md.transientRenderCache = this.#lastUpdateTransient;
1656
1821
  this.#contentContainer.addChild(md);
1657
1822
  captureItems?.push({ md, contentIndex: i, blockType: "thinking", lastText: thinkingText });
@@ -1666,6 +1831,9 @@ export class AssistantMessageComponent extends Container {
1666
1831
  hasRenderedContent ||= this.#showImages;
1667
1832
  }
1668
1833
  }
1834
+ for (const [index, block] of previousFigures) {
1835
+ if (this.#figureBlocks.get(index) !== block) block.dispose();
1836
+ }
1669
1837
 
1670
1838
  if (this.#shouldAnimateThinking(message)) {
1671
1839
  if (hasVisibleContent) this.#contentContainer.addChild(new Spacer(1));
@@ -1677,6 +1845,7 @@ export class AssistantMessageComponent extends Container {
1677
1845
  }
1678
1846
 
1679
1847
  this.#renderToolImages();
1848
+ this.#kittyPreviouslyDisplayed = undefined;
1680
1849
  const errorPresentation = resolveAssistantErrorPresentation(message);
1681
1850
  const hasToolCalls = message.content.some(c => c.type === "toolCall");
1682
1851
  if (errorPresentation.kind === "compact-recovered") {
@@ -28,6 +28,8 @@ export interface ChatTranscriptBuilderDeps {
28
28
  expandThinkingBlocks?: () => boolean;
29
29
  /** Session-scoped resolved destinations for model-authored Markdown links. */
30
30
  linkTargets?: ReadonlyMap<string, string>;
31
+ /** Draw charts under numeric tables; false for transcripts that are not the main session's. Default: true. */
32
+ tableCharts?: boolean;
31
33
  requestRender: () => void;
32
34
  }
33
35
  export declare class ChatTranscriptBuilder {
@@ -83,6 +83,8 @@ export interface ChatTranscriptBuilderDeps {
83
83
  expandThinkingBlocks?: () => boolean;
84
84
  /** Session-scoped resolved destinations for model-authored Markdown links. */
85
85
  linkTargets?: ReadonlyMap<string, string>;
86
+ /** Draw charts under numeric tables; false for transcripts that are not the main session's. Default: true. */
87
+ tableCharts?: boolean;
86
88
  requestRender: () => void;
87
89
  }
88
90
 
@@ -221,9 +223,7 @@ export class ChatTranscriptBuilder {
221
223
  }
222
224
  if (previous.canBeDisplacedBy(nextToolName)) {
223
225
  this.#todoSnapshot = null;
224
- if (this.container.canRemoveBlock(previous)) {
225
- this.container.removeChild(previous);
226
- }
226
+ this.container.removeChild(previous);
227
227
  previous.seal();
228
228
  return;
229
229
  }
@@ -419,6 +419,7 @@ export class ChatTranscriptBuilder {
419
419
  );
420
420
  assistantComponent.setImagesVisible(displayPreferences.showImages);
421
421
  assistantComponent.setToolResultImagesVisible(!displayPreferences.hideToolActivity);
422
+ assistantComponent.setTableChartsVisible(this.#deps.tableCharts !== false);
422
423
  this.#trackExpandable(assistantComponent);
423
424
  assistantComponent.pickReactionTarget(this.container.children);
424
425
  this.container.addChild(assistantComponent);
@@ -457,6 +458,7 @@ export class ChatTranscriptBuilder {
457
458
  );
458
459
  component.setImagesVisible(displayPreferences.showImages);
459
460
  component.setToolResultImagesVisible(!displayPreferences.hideToolActivity);
461
+ component.setTableChartsVisible(this.#deps.tableCharts !== false);
460
462
  this.#trackExpandable(component);
461
463
  this.container.addChild(component);
462
464
  lastAssistantComponent = component;
@@ -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
  }
@@ -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;
@@ -78,7 +78,7 @@ export interface ExecutionToolInput {
78
78
  /** Tool name for the head icon (`bash`, `eval`). */
79
79
  readonly name: string;
80
80
  readonly title: string;
81
- /** The command / cell source, shown once in the head. */
81
+ /** The command / cell source: the run box's input and the head's Copy command. */
82
82
  readonly command: string;
83
83
  readonly lang: string;
84
84
  /** Excluded from context (`!!`/`$$`). */
@@ -96,10 +96,11 @@ export interface ExecutionToolInput {
96
96
  readonly artifactError?: OutputArtifactError;
97
97
  }
98
98
  /**
99
- * A user `!`/`$` run as the agent's `tool` frame (§6.6): the command in the
100
- * head with a `you` badge (plus `not sent` when excluded from context), the
101
- * timer, exit chip and cancelled note; the body is the output as an `ansi`
102
- * mini terminal following its tail, then one quiet truncation line.
99
+ * A user `!`/`$` run as the agent's `tool` frame (§6.6): a head with a `you`
100
+ * badge (plus `not sent` when excluded from context) and the command for Copy
101
+ * command; the body is one run box — the source, the output as an `ansi` mini
102
+ * terminal following its tail, and the foot (state, time, truncation) — with
103
+ * any images below it. Timer, exit chip and cancelled note stay head data.
103
104
  */
104
105
  export declare function describeExecutionTool(input: ExecutionToolInput): NativeNode;
105
106
  /**