@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
@@ -1,4 +1,5 @@
1
- import { applyBackgroundToLine, padding, visibleWidth } from "../utils";
1
+ import { applyBackgroundToLine, padding, stripTerminalSequences, visibleWidth } from "../utils";
2
+ import type { AnimationFrame, TranscriptPresentationTarget } from "../chrome/transcript-container";
2
3
  import { type Component, Container } from "../tui";
3
4
  import { Disclosure } from "../components/disclosure";
4
5
  import { Markdown } from "../components/markdown";
@@ -57,7 +58,7 @@ const OSC133_ZONE_CLOSE = OSC133_ZONE_END + OSC133_COMMAND_START + OSC133_COMMAN
57
58
 
58
59
  /** How a user bubble styles its prose and chips (see {@link userBubbleColor}). */
59
60
  export interface UserBubbleOptions {
60
- /** Materialized `file://` targets per attached image, indexed by chip number. */
61
+ /** Filesystem paths for attached image chips, indexed by chip number. */
61
62
  imageLinks?: readonly (string | undefined)[];
62
63
  /** The message's attached images in chip order (`#1` first); a native bubble shows them. */
63
64
  images?: readonly ImageContent[];
@@ -124,7 +125,8 @@ export function userBubbleColor(
124
125
  * (see {@link ReactionTarget}) drawn right-aligned in the bubble's top padding row;
125
126
  * a live-steered message carries a `*` marker left-aligned in the same row.
126
127
  */
127
- export class UserMessageComponent extends Container implements ReactionTarget {
128
+ export class UserMessageComponent extends Container implements ReactionTarget, TranscriptPresentationTarget {
129
+ #allocation = Number.POSITIVE_INFINITY;
128
130
  // Memoized OSC 133 zone wrapping keyed on the underlying container render
129
131
  // (same source ref ⇒ identical rows ⇒ reuse the wrapped copy). Keeps this
130
132
  // component reference-stable for the transcript's incremental assembly and
@@ -177,6 +179,12 @@ export class UserMessageComponent extends Container implements ReactionTarget {
177
179
  this.addChild(markdown);
178
180
  }
179
181
 
182
+ override releaseRenderCaches(): void {
183
+ this.#zoneSource = undefined;
184
+ this.#zoneLines = undefined;
185
+ super.releaseRenderCaches();
186
+ }
187
+
180
188
  setReaction(emoji: string): void {
181
189
  if (this.#reaction === emoji) return;
182
190
  this.#reaction = emoji;
@@ -281,11 +289,47 @@ export class UserMessageComponent extends Container implements ReactionTarget {
281
289
  return applyBackgroundToLine(` ${marker}${padding(gap)}${emoji}`, width, this.#bgColor);
282
290
  }
283
291
 
292
+ /** Apply the transcript allocator's current viewport reservation. */
293
+ setTranscriptAllocation(rows: number, _frame?: AnimationFrame): void {
294
+ this.#allocation = Math.max(0, Math.trunc(rows));
295
+ }
296
+
297
+ isTranscriptBlockFinalized(): boolean {
298
+ return true;
299
+ }
300
+
301
+ /**
302
+ * Single-row emergency representation for the overflow transcript layout:
303
+ * renders the first content line instead of the blank top padding row, and
304
+ * omits OSC 133 shell integration markers so moving live blocks across the
305
+ * viewport does not leave stale prompt marks on lines it no longer occupies.
306
+ */
307
+ renderTranscriptBlockEmergencyRow(width: number): string | undefined {
308
+ return this.#selectEmergencyRow(super.render(width));
309
+ }
310
+
311
+ #selectEmergencyRow(lines: readonly string[]): string | undefined {
312
+ if (lines.length === 0) return undefined;
313
+ if (lines.length >= 3) {
314
+ for (let i = 1; i < lines.length - 1; i++) {
315
+ if (stripTerminalSequences(lines[i]!).trim().length > 0) {
316
+ return lines[i];
317
+ }
318
+ }
319
+ return lines[1];
320
+ }
321
+ return lines[0];
322
+ }
323
+
284
324
  override render(width: number): readonly string[] {
285
325
  const lines = super.render(width);
286
326
  if (lines.length === 0) {
287
327
  return lines;
288
328
  }
329
+ if (this.#allocation === 1) {
330
+ const emergency = this.#selectEmergencyRow(lines);
331
+ if (emergency !== undefined) return [emergency];
332
+ }
289
333
  if (this.#zoneSource === lines && this.#zoneLines !== undefined) {
290
334
  return this.#zoneLines;
291
335
  }
@@ -337,6 +381,10 @@ class SyntheticSummary implements Component {
337
381
  this.#summary = summary;
338
382
  }
339
383
 
384
+ releaseRenderCaches(): void {
385
+ this.#cache = undefined;
386
+ }
387
+
340
388
  invalidate(): void {
341
389
  this.#cache = undefined;
342
390
  }
@@ -425,6 +473,10 @@ export class CollapsedSyntheticMessageComponent implements Component {
425
473
  this.#disclosure.invalidate();
426
474
  }
427
475
 
476
+ releaseRenderCaches(): void {
477
+ this.#disclosure.releaseRenderCaches();
478
+ }
479
+
428
480
  dispose(): void {
429
481
  this.#disclosure.dispose();
430
482
  }
@@ -10,6 +10,59 @@ export function sanitizeStatusText(text: string): string {
10
10
  return sanitizeDisplaySingleLine(text).replace(/ +/g, " ").trim();
11
11
  }
12
12
 
13
+ /** 7-bit SGR (`ESC [ <params> m`) — the one escape family a hook status may keep. */
14
+ const SGR_RE = /\x1b\[[0-9;:]*m/g;
15
+ const SGR_RESET = "\x1b[0m";
16
+ const WHITESPACE_RE = /\s/;
17
+
18
+ /**
19
+ * Like {@link sanitizeStatusText}, but keeps 7-bit SGR so extensions can colour
20
+ * `ctx.ui.setStatus` text. Every other escape (OSC links/titles, DCS/PM/APC,
21
+ * 7- and 8-bit CSI), C0/C1, lone surrogates, newlines and tabs are sanitized at
22
+ * least as strictly as in `sanitizeStatusText`; space runs collapse and edges
23
+ * trim (`String#trim` whitespace) by visible text, regardless of SGR placement.
24
+ * A styled result always ends with `\x1b[0m`.
25
+ */
26
+ export function sanitizeHookStatusText(input: string): string {
27
+ // Drop lone surrogates before splitting, as `sanitizeText` does, so stripped escapes
28
+ // cannot fuse surrogate halves from neighbouring chunks into a new character.
29
+ const wellFormed = input.toWellFormed();
30
+ const text = wellFormed === input ? input : wellFormed.replaceAll("\ufffd", "");
31
+ SGR_RE.lastIndex = 0;
32
+ if (!SGR_RE.test(text)) return sanitizeStatusText(text); // byte-identical for unstyled input
33
+ let out = "";
34
+ // SGR codes and whitespace seen since the last visible char, in source order.
35
+ let pending = "";
36
+ let lastWasSpace = false;
37
+ let hasVisible = false;
38
+ let cursor = 0;
39
+ SGR_RE.lastIndex = 0;
40
+ for (;;) {
41
+ const match = SGR_RE.exec(text);
42
+ // `sanitizeText` runs `Bun.stripANSI` only when an ESC is present; a chunk between SGR
43
+ // codes often has none, so strip explicitly to drop 8-bit (C1) sequence payloads.
44
+ const chunk = sanitizeDisplaySingleLine(Bun.stripANSI(text.slice(cursor, match ? match.index : text.length)));
45
+ for (const ch of chunk) {
46
+ if (WHITESPACE_RE.test(ch)) {
47
+ // Leading whitespace drops; ASCII space runs collapse even across SGR codes.
48
+ if (hasVisible && !(ch === " " && lastWasSpace)) pending += ch;
49
+ lastWasSpace = ch === " ";
50
+ continue;
51
+ }
52
+ out += pending + ch;
53
+ pending = "";
54
+ lastWasSpace = false;
55
+ hasVisible = true;
56
+ }
57
+ if (!match) break;
58
+ pending += match[0];
59
+ cursor = match.index + match[0].length;
60
+ }
61
+ if (!hasVisible) return "";
62
+ // Trailing whitespace trims; trailing SGR codes stay (they never contain whitespace).
63
+ return out + pending.replace(/\s+/g, "") + SGR_RESET;
64
+ }
65
+
13
66
  // ═══════════════════════════════════════════════════════════════════════════
14
67
  // Tab Bar Theme
15
68
  // ═══════════════════════════════════════════════════════════════════════════
@@ -63,6 +63,12 @@ export interface TranscriptViewportSpan {
63
63
  export declare class TranscriptContainer extends Container {
64
64
  #private;
65
65
  addChild(component: Component): void;
66
+ /**
67
+ * `#entries` mirrors `children`; move both together or the next `#syncEntries`
68
+ * pays a full O(n) rebuild.
69
+ */
70
+ insertChildAt(component: Component, index: number): void;
71
+ /** Removes `component` if {@link canRemoveBlock} allows it; otherwise does nothing. */
66
72
  removeChild(component: Component): void;
67
73
  clear(): void;
68
74
  setToolActivityVisible(visible: boolean): void;
@@ -58,6 +58,12 @@ export interface AppendOnlyTranscriptBlock {
58
58
 
59
59
  interface FinalizableBlock {
60
60
  isTranscriptBlockFinalized?(): boolean;
61
+ /**
62
+ * A finalized block whose rows still wait on async work (an SVG figure's
63
+ * raster). Retirement holds until it lands: committed rows can never be
64
+ * repainted, so retiring early would freeze a placeholder into scrollback.
65
+ */
66
+ isTranscriptBlockPending?(): boolean;
61
67
  /** Render the row that must remain represented under emergency viewport pressure. */
62
68
  renderTranscriptBlockEmergencyRow?(width: number): string | undefined;
63
69
  }
@@ -129,7 +135,7 @@ const EMPTY_STABLE_ROWS: readonly TranscriptStableRow[] = [];
129
135
 
130
136
  function isFinalized(component: Component): boolean {
131
137
  const block = component as Component & FinalizableBlock;
132
- return block.isTranscriptBlockFinalized?.() ?? true;
138
+ return (block.isTranscriptBlockFinalized?.() ?? true) && block.isTranscriptBlockPending?.() !== true;
133
139
  }
134
140
 
135
141
  function blockMode(component: Component): TranscriptBlockMode {
@@ -208,6 +214,7 @@ export class TranscriptContainer extends Container {
208
214
  */
209
215
  #frameRows = new Map<TranscriptEntry, readonly string[]>();
210
216
  #frameRowsWidth = 0;
217
+ #releaseFailures = new WeakSet<Component>();
211
218
  /** The `children` array `#entries` last mirrored; see {@link #syncEntries}. */
212
219
  #syncedChildren: Component[] | undefined;
213
220
  /** Forces the next {@link #syncEntries} to compare every entry, not just the live tail. */
@@ -229,15 +236,40 @@ export class TranscriptContainer extends Container {
229
236
  stableFrozen: false,
230
237
  imageScan: undefined,
231
238
  });
232
- // Callers may splice a just-added block into place (insert after an
233
- // anchor); re-check the whole list once instead of trusting positions.
239
+ // `children` is public and may be edited externally; re-check the whole
240
+ // list once instead of trusting positions. Insert-after-anchor callers
241
+ // use insertChildAt, which keeps both arrays in lockstep.
234
242
  this.#entriesUnverified = true;
235
243
  }
236
244
 
245
+ /**
246
+ * `#entries` mirrors `children`; move both together or the next `#syncEntries`
247
+ * pays a full O(n) rebuild.
248
+ */
249
+ insertChildAt(component: Component, index: number): void {
250
+ this.addChild(component);
251
+ const from = this.children.length - 1;
252
+ if (index >= from) {
253
+ this.#entriesUnverified = false;
254
+ return;
255
+ }
256
+ const [moved] = this.children.splice(from, 1);
257
+ if (moved) this.children.splice(index, 0, moved);
258
+ const [entry] = this.#entries.splice(this.#entries.length - 1, 1);
259
+ if (entry) this.#entries.splice(index, 0, entry);
260
+ // Both arrays moved in lockstep, so the live-tail check applies again.
261
+ this.#entriesUnverified = false;
262
+ }
263
+
264
+ /** Removes `component` if {@link canRemoveBlock} allows it; otherwise does nothing. */
237
265
  override removeChild(component: Component): void {
238
- if (this.children.indexOf(component) < 0 || !this.canRemoveBlock(component)) return;
239
- super.removeChild(component);
240
- this.#entries = this.#entries.filter(candidate => candidate.component !== component);
266
+ const index = this.#removableIndex(component);
267
+ if (index < 0) return;
268
+ // #removableIndex synced entries, which now mirror children index for
269
+ // index. This class renders through its own entries, never Container's
270
+ // memoized render, so splicing children directly is the whole removal.
271
+ this.children.splice(index, 1);
272
+ this.#entries.splice(index, 1);
241
273
  this.#frontier = Math.min(this.#frontier, this.#entries.length);
242
274
  this.#childStartRows.delete(component);
243
275
  }
@@ -292,14 +324,24 @@ export class TranscriptContainer extends Container {
292
324
 
293
325
  /** Whether a transient block may be discarded without leaving tape history. */
294
326
  canRemoveBlock(component: Component): boolean {
327
+ return this.#removableIndex(component) >= 0;
328
+ }
329
+
330
+ /**
331
+ * Syncs entries, then returns `component`'s index if it can be discarded
332
+ * without leaving tape history (not committed, not emitted, not in an
333
+ * offered batch), else -1.
334
+ */
335
+ #removableIndex(component: Component): number {
295
336
  this.#syncEntries();
296
- const index = this.#entries.findIndex(entry => entry.component === component);
297
- if (index < 0) return false;
337
+ // Removable blocks are transient ones near the live tail, so search from there.
338
+ const index = this.#entries.findLastIndex(entry => entry.component === component);
339
+ if (index < 0) return -1;
298
340
  const entry = this.#entries[index]!;
299
- if (entry.state === "committed" || entry.emitted > 0) return false;
300
- if (this.#offered?.kind === "commit" && index < this.#offered.end) return false;
301
- if (this.#offered?.kind === "append" && index === this.#offered.entry) return false;
302
- return true;
341
+ if (entry.state === "committed" || entry.emitted > 0) return -1;
342
+ if (this.#offered?.kind === "commit" && index < this.#offered.end) return -1;
343
+ if (this.#offered?.kind === "append" && index === this.#offered.entry) return -1;
344
+ return index;
303
345
  }
304
346
 
305
347
  /**
@@ -586,7 +628,10 @@ export class TranscriptContainer extends Container {
586
628
  popLoopPhase();
587
629
  }
588
630
  this.#replayPending = false;
589
- if (rows.length === 0) return undefined;
631
+ if (rows.length === 0) {
632
+ this.#releaseCommittedRenderCaches();
633
+ return undefined;
634
+ }
590
635
  const batch: HistoryBatch = { id: this.#nextBatchId++, rows, kind: "replay" };
591
636
  this.#offered = { batch, kind: "replay" };
592
637
  return batch;
@@ -764,6 +809,8 @@ export class TranscriptContainer extends Container {
764
809
  this.#retireEntry(this.#entries[index]!);
765
810
  }
766
811
  this.#frontier = offered.end;
812
+ } else {
813
+ this.#releaseCommittedRenderCaches();
767
814
  }
768
815
  this.#offered = undefined;
769
816
  if (this.#replayRequested) this.#startReplay();
@@ -783,6 +830,7 @@ export class TranscriptContainer extends Container {
783
830
  const entry = this.#entries[index]!;
784
831
  this.#setAllocation(entry.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
785
832
  const block = trimBlankEdges(entry.component.render(width));
833
+ if (entry.state === "committed") this.#releaseRenderCaches(entry);
786
834
  if (block.length === 0) continue;
787
835
  if (rows.length > 0) rows.unshift("");
788
836
  rows.unshift(...block);
@@ -830,6 +878,7 @@ export class TranscriptContainer extends Container {
830
878
  for (const entry of this.#entries) {
831
879
  this.#setAllocation(entry.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
832
880
  const block = this.#renderEntry(entry, width);
881
+ if (entry.state === "committed") this.#releaseRenderCaches(entry);
833
882
  if (block.length === 0) continue;
834
883
  if (rows.length > 0) rows.push("");
835
884
  this.#childStartRows.set(entry.component, rows.length);
@@ -1071,6 +1120,29 @@ export class TranscriptContainer extends Container {
1071
1120
  entry.stableRows = EMPTY_STABLE_ROWS;
1072
1121
  entry.renderedStableByWidth = new Map();
1073
1122
  entry.stableRowCountByWidth = new Map();
1123
+ this.#frameRows.delete(entry);
1124
+ this.#releaseRenderCaches(entry);
1125
+ }
1126
+
1127
+ #releaseCommittedRenderCaches(): void {
1128
+ for (let index = 0; index < this.#frontier; index++) {
1129
+ this.#releaseRenderCaches(this.#entries[index]!);
1130
+ }
1131
+ }
1132
+
1133
+ #releaseRenderCaches(entry: TranscriptEntry): void {
1134
+ const release = entry.component.releaseRenderCaches;
1135
+ if (release === undefined) return;
1136
+ try {
1137
+ release.call(entry.component);
1138
+ } catch (err) {
1139
+ if (this.#releaseFailures.has(entry.component)) return;
1140
+ this.#releaseFailures.add(entry.component);
1141
+ logger.warn("Transcript block failed to release render caches", {
1142
+ component: entry.component.constructor.name,
1143
+ error: String(err),
1144
+ });
1145
+ }
1074
1146
  }
1075
1147
 
1076
1148
  #startReplay(): void {
@@ -1172,6 +1244,14 @@ export class TranscriptContainer extends Container {
1172
1244
  continue;
1173
1245
  }
1174
1246
  this.#setAllocation(candidate.entry.component, 1, frame);
1247
+ const emergency = (
1248
+ candidate.entry.component as Component & FinalizableBlock
1249
+ ).renderTranscriptBlockEmergencyRow?.(width);
1250
+ if (emergency !== undefined) {
1251
+ output.push(emergency);
1252
+ owners.push(candidate.entry.component);
1253
+ continue;
1254
+ }
1175
1255
  const rendered = this.#renderEntry(candidate.entry, width).slice(
1176
1256
  this.#projectedEmittedRowCount(candidate.entry, candidate.index, width),
1177
1257
  );
@@ -30,6 +30,7 @@ export declare class Box implements Component {
30
30
  setBgFn(bgFn?: (text: string) => string): void;
31
31
  setBorder(border?: BoxBorder): void;
32
32
  invalidate(): void;
33
+ releaseRenderCaches(): void;
33
34
  /**
34
35
  * A `card` when the box has a border or background: the background token
35
36
  * picks tone/role (user message, tool state, selection), a border colour
@@ -135,6 +135,11 @@ export class Box implements Component {
135
135
  }
136
136
  }
137
137
 
138
+ releaseRenderCaches(): void {
139
+ this.#invalidateCache();
140
+ for (const child of this.children) child.releaseRenderCaches?.();
141
+ }
142
+
138
143
  /**
139
144
  * A `card` when the box has a border or background: the background token
140
145
  * picks tone/role (user message, tool state, selection), a border colour
@@ -36,6 +36,7 @@ export declare class Disclosure implements Component {
36
36
  setExpanded(expanded: boolean): void;
37
37
  setIgnoreTight(ignore: boolean): this;
38
38
  invalidate(): void;
39
+ releaseRenderCaches(): void;
39
40
  dispose(): void;
40
41
  debugState(): Record<string, unknown>;
41
42
  get debugChildren(): readonly Component[];
@@ -100,6 +100,14 @@ export class Disclosure implements Component {
100
100
  this.#body?.invalidate?.();
101
101
  }
102
102
 
103
+ releaseRenderCaches(): void {
104
+ this.#cache = undefined;
105
+ this.#boundedCache = undefined;
106
+ this.#summary?.releaseRenderCaches?.();
107
+ this.#collapsedBody?.releaseRenderCaches?.();
108
+ this.#body?.releaseRenderCaches?.();
109
+ }
110
+
103
111
  dispose(): void {
104
112
  if (this.#disposed) return;
105
113
  this.#disposed = true;
@@ -1,5 +1,5 @@
1
1
  import { type AutocompleteProvider } from "../autocomplete";
2
- import type { TspEditorDecoration, TspTone } from "@linxiraos/pi-wire";
2
+ import type { TspEditorDecoration, TspText, TspTone } from "@linxiraos/pi-wire";
3
3
  import { type DescribeContext, type NativeChild, type NativeNode, type NativeTextEdit, type NativeUiEvent } from "../native/node";
4
4
  import type { SymbolTheme } from "../symbols";
5
5
  import { type Component, type Focusable } from "../tui";
@@ -106,8 +106,11 @@ export declare class Editor implements Component, Focusable {
106
106
  * dialog's text field; only the prompt composer claims `zeta.editor`).
107
107
  */
108
108
  describeLayout: ((input: NativeNode, cx: DescribeContext) => NativeEditorLayout) | undefined;
109
- /** TSP placeholder for the empty buffer; natively it replaces the rotating ANSI {@link placeholder} hints. */
110
- describePlaceholder: (() => string) | undefined;
109
+ /**
110
+ * TSP placeholder for the empty buffer; natively it replaces the rotating ANSI {@link placeholder} hints.
111
+ * Return a stable value (the same spans array) while it is unchanged: the editor node is reused by identity.
112
+ */
113
+ describePlaceholder: (() => TspText) | undefined;
111
114
  /** Called with the selected text when Visual mode yanks, so hosts can reach the system
112
115
  * clipboard — `packages/tui` deliberately has no clipboard dependency of its own. */
113
116
  onYank?: (text: string) => void;
@@ -146,6 +149,12 @@ export declare class Editor implements Component, Focusable {
146
149
  * cursor. The host styles it (ANSI allowed). Re-evaluated on every render, so hosts can derive
147
150
  * it from live state. */
148
151
  placeholder?: () => string | undefined;
152
+ /** Whole-message prediction for the next prompt, painted as ghost text after the cursor while
153
+ * the single-line buffer is a strict prefix of it, the cursor sits at its end, and no
154
+ * autocomplete is open. A shown prediction replaces the {@link placeholder}; Tab or Right inserts
155
+ * the rest without submitting. Must be a single line. Re-evaluated on every render, so hosts can
156
+ * derive it from live state. */
157
+ prediction?: () => string | undefined;
149
158
  constructor(theme: EditorTheme);
150
159
  /** Return bounded editor content, cursor, and transient child state for debug inspection. */
151
160
  debugState(): Record<string, unknown>;
@@ -250,6 +259,8 @@ export declare class Editor implements Component, Focusable {
250
259
  get atoms(): ReadonlyMap<string, string>;
251
260
  /** Monotonic buffer-content revision for render caches. Cursor-only movement does not advance it. */
252
261
  get textRevision(): number;
262
+ /** Monotonic atom-table revision for render caches: advances whenever {@link atoms} is mutated or replaced. */
263
+ get atomsRevision(): number;
253
264
  /** Whether the buffer text equals `value`, without `getText()`'s full join —
254
265
  * O(1) for the hot per-keystroke probes against short single-line values. */
255
266
  textEquals(value: string): boolean;
@@ -285,7 +296,7 @@ export declare class Editor implements Component, Focusable {
285
296
  * Mirrors the transient state the key dispatch tears down before this action so the two
286
297
  * cannot diverge: a pending character jump is cancelled by any other key, and an open
287
298
  * spelling-assist popup is dismissed by anything that is not one of its accept keys (its
288
- * debounced refresh skips assist mode, so a surviving list would hang around forever).
299
+ * refresh skips assist mode, so a surviving list would hang around forever).
289
300
  * While Vim owns the buffer (Normal or Visual) the operation is Vim's `x` — deleting the
290
301
  * selection and returning to Normal in Visual mode, the grapheme under the cursor
291
302
  * otherwise. Only Insert mode and Vim-off editors delete straight through. */