@linxiraos/pi-tui 1.1.26 → 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 (423) hide show
  1. package/CHANGELOG.md +51 -491
  2. package/README.md +13 -7
  3. package/dist/types/active-terminal.d.ts +30 -0
  4. package/dist/types/app-keybindings.d.ts +7 -2
  5. package/dist/types/apps/cleanse-board.d.ts +1 -1
  6. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  7. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  8. package/dist/types/apps/git/git-tui.d.ts +5 -4
  9. package/dist/types/apps/git/help.d.ts +3 -1
  10. package/dist/types/apps/if-bench-board.d.ts +2 -2
  11. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  12. package/dist/types/autocomplete.d.ts +5 -2
  13. package/dist/types/charts/chart-plan.d.ts +674 -0
  14. package/dist/types/charts/chart-svg.d.ts +59 -0
  15. package/dist/types/charts/table-data.d.ts +175 -0
  16. package/dist/types/chat/assistant-message.d.ts +11 -1
  17. package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
  18. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  19. package/dist/types/chat/execution-shared.d.ts +6 -5
  20. package/dist/types/chat/fence-figure.d.ts +42 -0
  21. package/dist/types/chat/figure-markdown.d.ts +42 -0
  22. package/dist/types/chat/messages.d.ts +1 -1
  23. package/dist/types/chat/svg-figure.d.ts +33 -0
  24. package/dist/types/chat/svg-source.d.ts +49 -0
  25. package/dist/types/chat/table-chart.d.ts +89 -0
  26. package/dist/types/chat/tool-execution.d.ts +2 -0
  27. package/dist/types/chat/user-message.d.ts +17 -4
  28. package/dist/types/chrome/shared.d.ts +9 -0
  29. package/dist/types/chrome/status-notice.d.ts +15 -3
  30. package/dist/types/chrome/transcript-container.d.ts +6 -0
  31. package/dist/types/components/box.d.ts +1 -0
  32. package/dist/types/components/disclosure.d.ts +1 -0
  33. package/dist/types/components/editor.d.ts +23 -5
  34. package/dist/types/components/form.d.ts +1 -0
  35. package/dist/types/components/image.d.ts +26 -44
  36. package/dist/types/components/input.d.ts +2 -1
  37. package/dist/types/components/key-value-list.d.ts +1 -0
  38. package/dist/types/components/layout/row.d.ts +1 -0
  39. package/dist/types/components/layout/stack.d.ts +1 -0
  40. package/dist/types/components/loader.d.ts +5 -6
  41. package/dist/types/components/markdown.d.ts +47 -5
  42. package/dist/types/components/scroll-view.d.ts +1 -0
  43. package/dist/types/components/section.d.ts +1 -0
  44. package/dist/types/components/table.d.ts +1 -0
  45. package/dist/types/components/text.d.ts +1 -0
  46. package/dist/types/components/tree-view.d.ts +7 -0
  47. package/dist/types/components/truncated-text.d.ts +1 -0
  48. package/dist/types/fuzzy.d.ts +29 -1
  49. package/dist/types/key-hint-format.d.ts +9 -1
  50. package/dist/types/keybinding-matchers.d.ts +3 -0
  51. package/dist/types/keybindings.d.ts +1 -1
  52. package/dist/types/kitty-graphics.d.ts +2 -2
  53. package/dist/types/loop-watchdog.d.ts +8 -7
  54. package/dist/types/multiplexers/cmux.d.ts +16 -0
  55. package/dist/types/multiplexers/herdr.d.ts +26 -0
  56. package/dist/types/multiplexers/notify.d.ts +13 -0
  57. package/dist/types/multiplexers/orca.d.ts +12 -0
  58. package/dist/types/multiplexers/screen.d.ts +9 -0
  59. package/dist/types/multiplexers/tmux.d.ts +29 -0
  60. package/dist/types/multiplexers/types.d.ts +100 -0
  61. package/dist/types/multiplexers/wmux.d.ts +12 -0
  62. package/dist/types/multiplexers/zellij.d.ts +12 -0
  63. package/dist/types/native/apply.d.ts +4 -3
  64. package/dist/types/native/backend.d.ts +5 -4
  65. package/dist/types/native/blobs.d.ts +7 -3
  66. package/dist/types/native/encode.d.ts +8 -6
  67. package/dist/types/native/node.d.ts +36 -5
  68. package/dist/types/native/overlay.d.ts +8 -3
  69. package/dist/types/native/settle.d.ts +4 -3
  70. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  71. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  72. package/dist/types/overlays/agent-hub.d.ts +3 -0
  73. package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
  74. package/dist/types/overlays/agents-hub.d.ts +9 -0
  75. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  76. package/dist/types/overlays/annotation-types.d.ts +2 -0
  77. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  78. package/dist/types/overlays/btw-history-panel.d.ts +13 -1
  79. package/dist/types/overlays/copy-selector.d.ts +1 -0
  80. package/dist/types/overlays/history-search.d.ts +10 -0
  81. package/dist/types/overlays/hook-selector.d.ts +9 -4
  82. package/dist/types/overlays/hub-frame.d.ts +4 -1
  83. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  84. package/dist/types/overlays/model-browser.d.ts +54 -7
  85. package/dist/types/overlays/model-hub.d.ts +37 -0
  86. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  87. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  88. package/dist/types/overlays/report-panel.d.ts +68 -0
  89. package/dist/types/overlays/rewind-selector.d.ts +1 -0
  90. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  91. package/dist/types/overlays/session-selector.d.ts +6 -2
  92. package/dist/types/overlays/settings-defs.d.ts +1 -0
  93. package/dist/types/overlays/thinking-selector.d.ts +2 -2
  94. package/dist/types/overlays/usage-dashboard.d.ts +12 -8
  95. package/dist/types/overlays/usage-display.d.ts +3 -1
  96. package/dist/types/prompt/composer-cache.d.ts +0 -16
  97. package/dist/types/prompt/composer.d.ts +18 -3
  98. package/dist/types/prompt/custom-editor.d.ts +25 -13
  99. package/dist/types/prompt/image-source.d.ts +2 -1
  100. package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
  101. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  102. package/dist/types/prompt/welcome.d.ts +7 -2
  103. package/dist/types/render/composite.d.ts +2 -0
  104. package/dist/types/render/output-pane.d.ts +1 -0
  105. package/dist/types/render/render-utils.d.ts +1 -0
  106. package/dist/types/render/tool-card.d.ts +6 -0
  107. package/dist/types/render/width-aware-text.d.ts +2 -0
  108. package/dist/types/setup/scenes/types.d.ts +1 -0
  109. package/dist/types/space-hold.d.ts +40 -25
  110. package/dist/types/status-line/component.d.ts +3 -0
  111. package/dist/types/status-line/context-usage.d.ts +7 -5
  112. package/dist/types/status-line/host.d.ts +4 -1
  113. package/dist/types/status-line/segments.d.ts +2 -0
  114. package/dist/types/status-line/types.d.ts +14 -3
  115. package/dist/types/terminal-capabilities.d.ts +41 -36
  116. package/dist/types/terminal-multiplexer.d.ts +127 -14
  117. package/dist/types/terminal.d.ts +23 -16
  118. package/dist/types/theme/active-symbols.d.ts +1 -1
  119. package/dist/types/theme/schema.d.ts +11 -2
  120. package/dist/types/theme/symbols.d.ts +1 -1
  121. package/dist/types/theme/theme-class.d.ts +7 -0
  122. package/dist/types/theme/theme.d.ts +1 -1
  123. package/dist/types/theme/tui-adapters.d.ts +3 -0
  124. package/dist/types/thinking.d.ts +1 -1
  125. package/dist/types/tmux.d.ts +11 -4
  126. package/dist/types/tools/bash.d.ts +7 -2
  127. package/dist/types/tools/daemon.d.ts +1 -1
  128. package/dist/types/tools/eval.d.ts +22 -3
  129. package/dist/types/tools/glob.d.ts +4 -0
  130. package/dist/types/tools/native-view.d.ts +47 -0
  131. package/dist/types/tools/output-meta.d.ts +11 -1
  132. package/dist/types/tools/renderer.d.ts +37 -2
  133. package/dist/types/tools/streaming-output.d.ts +20 -1
  134. package/dist/types/tools/task.d.ts +2 -3
  135. package/dist/types/tools/write.d.ts +9 -2
  136. package/dist/types/tools/yield.d.ts +22 -0
  137. package/dist/types/tui.d.ts +36 -0
  138. package/dist/types/utils.d.ts +6 -0
  139. package/dist/types/vim.d.ts +19 -5
  140. package/dist/types/windows-input-mode.d.ts +33 -1
  141. package/package.json +13 -9
  142. package/src/active-terminal.d.ts +30 -0
  143. package/src/active-terminal.ts +80 -0
  144. package/src/app-keybindings.d.ts +7 -2
  145. package/src/app-keybindings.ts +7 -2
  146. package/src/apps/autoresearch-dashboard.ts +7 -3
  147. package/src/apps/cleanse-board.ts +2 -2
  148. package/src/apps/cleanse-picker.ts +1 -1
  149. package/src/apps/debug/log-viewer.ts +188 -72
  150. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  151. package/src/apps/debug/raw-sse.ts +39 -5
  152. package/src/apps/git/diff-pane.ts +96 -23
  153. package/src/apps/git/git-tui.ts +23 -16
  154. package/src/apps/git/help.ts +29 -20
  155. package/src/apps/git/sidebar.ts +8 -3
  156. package/src/apps/if-bench-board.ts +2 -2
  157. package/src/apps/ps-top.ts +2 -2
  158. package/src/apps/setup-model-picker.ts +1 -1
  159. package/src/autocomplete.d.ts +5 -2
  160. package/src/autocomplete.ts +67 -43
  161. package/src/bracketed-paste.ts +10 -3
  162. package/src/charts/chart-plan.d.ts +674 -0
  163. package/src/charts/chart-plan.ts +5081 -0
  164. package/src/charts/chart-svg.d.ts +59 -0
  165. package/src/charts/chart-svg.ts +4657 -0
  166. package/src/charts/table-data.d.ts +175 -0
  167. package/src/charts/table-data.ts +1478 -0
  168. package/src/chat/advisor-message.ts +11 -0
  169. package/src/chat/assistant-message.d.ts +11 -1
  170. package/src/chat/assistant-message.ts +245 -54
  171. package/src/chat/chat-transcript-builder.d.ts +3 -0
  172. package/src/chat/chat-transcript-builder.ts +9 -3
  173. package/src/chat/compaction-summary-message.d.ts +1 -0
  174. package/src/chat/compaction-summary-message.ts +8 -0
  175. package/src/chat/execution-shared.d.ts +6 -5
  176. package/src/chat/execution-shared.ts +42 -25
  177. package/src/chat/fence-figure.d.ts +42 -0
  178. package/src/chat/fence-figure.ts +80 -0
  179. package/src/chat/figure-markdown.d.ts +42 -0
  180. package/src/chat/figure-markdown.ts +174 -0
  181. package/src/chat/messages.d.ts +1 -1
  182. package/src/chat/messages.ts +1 -1
  183. package/src/chat/read-tool-group.ts +6 -4
  184. package/src/chat/skill-message.ts +6 -0
  185. package/src/chat/svg-figure.d.ts +33 -0
  186. package/src/chat/svg-figure.ts +399 -0
  187. package/src/chat/svg-source.d.ts +49 -0
  188. package/src/chat/svg-source.ts +188 -0
  189. package/src/chat/table-chart.d.ts +89 -0
  190. package/src/chat/table-chart.ts +294 -0
  191. package/src/chat/tool-execution.d.ts +2 -0
  192. package/src/chat/tool-execution.ts +86 -7
  193. package/src/chat/user-message.d.ts +17 -4
  194. package/src/chat/user-message.ts +71 -8
  195. package/src/chrome/shared.ts +53 -0
  196. package/src/chrome/status-notice.ts +25 -7
  197. package/src/chrome/transcript-container.d.ts +6 -0
  198. package/src/chrome/transcript-container.ts +162 -19
  199. package/src/components/box.d.ts +1 -0
  200. package/src/components/box.ts +5 -0
  201. package/src/components/disclosure.d.ts +1 -0
  202. package/src/components/disclosure.ts +8 -0
  203. package/src/components/editor.d.ts +23 -5
  204. package/src/components/editor.ts +316 -47
  205. package/src/components/form.d.ts +1 -0
  206. package/src/components/form.ts +8 -0
  207. package/src/components/image.d.ts +26 -44
  208. package/src/components/image.ts +115 -152
  209. package/src/components/input.d.ts +2 -1
  210. package/src/components/input.ts +17 -7
  211. package/src/components/key-value-list.d.ts +1 -0
  212. package/src/components/key-value-list.ts +4 -0
  213. package/src/components/layout/row.d.ts +1 -0
  214. package/src/components/layout/row.ts +7 -0
  215. package/src/components/layout/stack.d.ts +1 -0
  216. package/src/components/layout/stack.ts +7 -0
  217. package/src/components/loader.d.ts +5 -6
  218. package/src/components/loader.ts +10 -17
  219. package/src/components/markdown.d.ts +47 -5
  220. package/src/components/markdown.ts +786 -303
  221. package/src/components/scroll-view.d.ts +1 -0
  222. package/src/components/scroll-view.ts +5 -0
  223. package/src/components/section.d.ts +1 -0
  224. package/src/components/section.ts +5 -0
  225. package/src/components/settings-list.ts +2 -0
  226. package/src/components/table.d.ts +1 -0
  227. package/src/components/table.ts +4 -0
  228. package/src/components/text.d.ts +1 -0
  229. package/src/components/text.ts +7 -0
  230. package/src/components/tree-view.d.ts +7 -0
  231. package/src/components/tree-view.ts +38 -8
  232. package/src/components/truncated-text.d.ts +1 -0
  233. package/src/components/truncated-text.ts +5 -0
  234. package/src/desktop-notify.ts +3 -2
  235. package/src/fuzzy.d.ts +29 -1
  236. package/src/fuzzy.ts +115 -30
  237. package/src/hotkeys-markdown.ts +9 -2
  238. package/src/key-hint-format.d.ts +9 -1
  239. package/src/key-hint-format.ts +26 -11
  240. package/src/keybinding-matchers.d.ts +3 -0
  241. package/src/keybinding-matchers.ts +6 -2
  242. package/src/keybindings.d.ts +1 -1
  243. package/src/keybindings.ts +1 -1
  244. package/src/kitty-graphics.d.ts +2 -2
  245. package/src/kitty-graphics.ts +11 -5
  246. package/src/loop-watchdog.d.ts +8 -7
  247. package/src/loop-watchdog.ts +18 -13
  248. package/src/multiplexers/cmux.d.ts +16 -0
  249. package/src/multiplexers/cmux.ts +29 -0
  250. package/src/multiplexers/herdr.d.ts +26 -0
  251. package/src/multiplexers/herdr.ts +64 -0
  252. package/src/multiplexers/notify.d.ts +13 -0
  253. package/src/multiplexers/notify.ts +24 -0
  254. package/src/multiplexers/orca.d.ts +12 -0
  255. package/src/multiplexers/orca.ts +16 -0
  256. package/src/multiplexers/screen.d.ts +9 -0
  257. package/src/multiplexers/screen.ts +15 -0
  258. package/src/multiplexers/tmux.d.ts +29 -0
  259. package/src/multiplexers/tmux.ts +40 -0
  260. package/src/multiplexers/types.d.ts +100 -0
  261. package/src/multiplexers/types.ts +110 -0
  262. package/src/multiplexers/wmux.d.ts +12 -0
  263. package/src/multiplexers/wmux.ts +16 -0
  264. package/src/multiplexers/zellij.d.ts +12 -0
  265. package/src/multiplexers/zellij.ts +24 -0
  266. package/src/native/apply.d.ts +4 -3
  267. package/src/native/apply.ts +4 -3
  268. package/src/native/backend.d.ts +5 -4
  269. package/src/native/backend.ts +199 -22
  270. package/src/native/blobs.d.ts +7 -3
  271. package/src/native/blobs.ts +47 -12
  272. package/src/native/encode.d.ts +8 -6
  273. package/src/native/encode.ts +76 -34
  274. package/src/native/node.d.ts +36 -5
  275. package/src/native/node.ts +31 -5
  276. package/src/native/overlay.d.ts +8 -3
  277. package/src/native/overlay.ts +17 -4
  278. package/src/native/reconcile.ts +16 -4
  279. package/src/native/settle.d.ts +4 -3
  280. package/src/native/settle.ts +4 -3
  281. package/src/overlays/agent-hub-projection.d.ts +6 -5
  282. package/src/overlays/agent-hub-projection.ts +124 -13
  283. package/src/overlays/agent-hub-types.d.ts +9 -0
  284. package/src/overlays/agent-hub-types.ts +6 -0
  285. package/src/overlays/agent-hub.ts +48 -29
  286. package/src/overlays/agent-transcript-viewer.ts +93 -23
  287. package/src/overlays/agents-hub.ts +28 -8
  288. package/src/overlays/annotation-overlay.ts +293 -90
  289. package/src/overlays/annotation-types.ts +2 -0
  290. package/src/overlays/ask-dialog.ts +10 -17
  291. package/src/overlays/btw-history-panel.ts +136 -39
  292. package/src/overlays/copy-selector.d.ts +1 -0
  293. package/src/overlays/copy-selector.ts +32 -8
  294. package/src/overlays/extensions/extension-dashboard.ts +4 -4
  295. package/src/overlays/extensions/state-manager.ts +52 -12
  296. package/src/overlays/history-search.ts +100 -7
  297. package/src/overlays/hook-selector.ts +12 -5
  298. package/src/overlays/hub-frame.ts +19 -1
  299. package/src/overlays/jobs-panel.ts +33 -52
  300. package/src/overlays/model-browser.ts +367 -122
  301. package/src/overlays/model-hub.ts +435 -59
  302. package/src/overlays/move-overlay.ts +28 -14
  303. package/src/overlays/oauth-selector.ts +54 -70
  304. package/src/overlays/pause-screen.ts +2 -2
  305. package/src/overlays/plan-review-overlay.ts +208 -70
  306. package/src/overlays/plugin-settings.ts +3 -3
  307. package/src/overlays/report-panel.ts +191 -0
  308. package/src/overlays/rewind-selector.ts +88 -17
  309. package/src/overlays/session-observer-registry.d.ts +2 -0
  310. package/src/overlays/session-observer-registry.ts +10 -2
  311. package/src/overlays/session-selector.ts +64 -26
  312. package/src/overlays/settings-defs.ts +5 -4
  313. package/src/overlays/settings-selector.ts +33 -8
  314. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  315. package/src/overlays/stats-notice.ts +1 -1
  316. package/src/overlays/thinking-selector.ts +11 -10
  317. package/src/overlays/tree-selector.ts +41 -20
  318. package/src/overlays/usage-dashboard.ts +117 -58
  319. package/src/overlays/usage-display.ts +10 -4
  320. package/src/overlays/usage-row.ts +22 -8
  321. package/src/prompt/attachment-chips.ts +15 -11
  322. package/src/prompt/composer-attachments.ts +6 -4
  323. package/src/prompt/composer-cache.ts +7 -86
  324. package/src/prompt/composer.ts +37 -26
  325. package/src/prompt/custom-editor.ts +282 -105
  326. package/src/prompt/image-source.ts +2 -1
  327. package/src/prompt/interactive-context-helpers.ts +32 -18
  328. package/src/prompt/macos-spelling.ts +7 -0
  329. package/src/prompt/magic-keywords.ts +11 -2
  330. package/src/prompt/model-mention-autocomplete.ts +14 -14
  331. package/src/prompt/queued-messages.ts +6 -4
  332. package/src/prompt/tips.txt +13 -9
  333. package/src/prompt/welcome.ts +64 -46
  334. package/src/prompt/word-completion.ts +38 -5
  335. package/src/render/composite.d.ts +2 -0
  336. package/src/render/composite.ts +64 -0
  337. package/src/render/output-pane.d.ts +1 -0
  338. package/src/render/output-pane.ts +6 -0
  339. package/src/render/render-utils.d.ts +1 -0
  340. package/src/render/render-utils.ts +16 -1
  341. package/src/render/tool-card.d.ts +6 -0
  342. package/src/render/tool-card.ts +15 -0
  343. package/src/render/width-aware-text.d.ts +2 -0
  344. package/src/render/width-aware-text.ts +5 -0
  345. package/src/setup/scenes/model.ts +2 -1
  346. package/src/setup/scenes/types.ts +1 -0
  347. package/src/space-hold.d.ts +40 -25
  348. package/src/space-hold.ts +156 -52
  349. package/src/status-line/component.ts +292 -78
  350. package/src/status-line/context-usage.ts +21 -46
  351. package/src/status-line/host.ts +4 -1
  352. package/src/status-line/segments.ts +40 -22
  353. package/src/status-line/types.ts +15 -3
  354. package/src/terminal-capabilities.d.ts +41 -36
  355. package/src/terminal-capabilities.ts +137 -213
  356. package/src/terminal-multiplexer.d.ts +127 -14
  357. package/src/terminal-multiplexer.ts +112 -33
  358. package/src/terminal.d.ts +23 -16
  359. package/src/terminal.ts +203 -99
  360. package/src/theme/active-symbols.d.ts +1 -1
  361. package/src/theme/active-symbols.ts +1 -1
  362. package/src/theme/schema-validation.ts +1 -0
  363. package/src/theme/schema.d.ts +11 -2
  364. package/src/theme/schema.ts +14 -1
  365. package/src/theme/session-color.ts +1 -7
  366. package/src/theme/symbols.d.ts +1 -1
  367. package/src/theme/symbols.ts +11 -4
  368. package/src/theme/theme-class.d.ts +7 -0
  369. package/src/theme/theme-class.ts +26 -1
  370. package/src/theme/theme-schema.json +5 -1
  371. package/src/theme/theme.d.ts +1 -1
  372. package/src/theme/theme.ts +8 -4
  373. package/src/theme/tui-adapters.d.ts +3 -0
  374. package/src/theme/tui-adapters.ts +68 -0
  375. package/src/thinking.d.ts +1 -1
  376. package/src/thinking.ts +1 -1
  377. package/src/tmux.d.ts +11 -4
  378. package/src/tmux.ts +47 -11
  379. package/src/tools/ast-edit.ts +59 -35
  380. package/src/tools/ast-grep.ts +35 -16
  381. package/src/tools/bash.d.ts +7 -2
  382. package/src/tools/bash.ts +116 -47
  383. package/src/tools/daemon.d.ts +1 -1
  384. package/src/tools/daemon.ts +1 -1
  385. package/src/tools/default-renderer.ts +35 -19
  386. package/src/tools/edit.ts +142 -120
  387. package/src/tools/eval-format/index.ts +28 -2
  388. package/src/tools/eval-format/javascript.ts +7 -5
  389. package/src/tools/eval.d.ts +22 -3
  390. package/src/tools/eval.ts +364 -208
  391. package/src/tools/fetch.ts +5 -6
  392. package/src/tools/glob.d.ts +4 -0
  393. package/src/tools/glob.ts +33 -65
  394. package/src/tools/grep.ts +81 -48
  395. package/src/tools/index.ts +2 -0
  396. package/src/tools/lsp.ts +81 -59
  397. package/src/tools/mcp.ts +111 -68
  398. package/src/tools/native-view.d.ts +47 -0
  399. package/src/tools/native-view.ts +102 -1
  400. package/src/tools/output-meta.d.ts +11 -1
  401. package/src/tools/output-meta.ts +23 -3
  402. package/src/tools/read.ts +7 -5
  403. package/src/tools/renderer.d.ts +37 -2
  404. package/src/tools/renderer.ts +42 -2
  405. package/src/tools/streaming-output.d.ts +20 -1
  406. package/src/tools/streaming-output.ts +154 -50
  407. package/src/tools/task.d.ts +2 -3
  408. package/src/tools/task.ts +115 -62
  409. package/src/tools/todo.ts +119 -30
  410. package/src/tools/wait.ts +34 -24
  411. package/src/tools/web-search.ts +1 -1
  412. package/src/tools/write.d.ts +9 -2
  413. package/src/tools/write.ts +147 -95
  414. package/src/tools/yield.d.ts +22 -0
  415. package/src/tools/yield.ts +225 -0
  416. package/src/tui.d.ts +36 -0
  417. package/src/tui.ts +666 -217
  418. package/src/utils.d.ts +6 -0
  419. package/src/utils.ts +19 -0
  420. package/src/vim.d.ts +19 -5
  421. package/src/vim.ts +333 -18
  422. package/src/windows-input-mode.d.ts +33 -1
  423. package/src/windows-input-mode.ts +212 -6
@@ -97,6 +97,10 @@ class OptionalText implements Component {
97
97
  this.#text = new StyledText("", style, token);
98
98
  }
99
99
 
100
+ get value(): string {
101
+ return this.#value;
102
+ }
103
+
100
104
  describe(cx: DescribeContext): NativeNode {
101
105
  return this.#value ? this.#text.describe(cx) : this.#hidden;
102
106
  }
@@ -200,6 +204,10 @@ export class FormField implements Component, Focusable, MouseRoutable {
200
204
  return this.#focused;
201
205
  }
202
206
 
207
+ get hasError(): boolean {
208
+ return this.#error.value !== "";
209
+ }
210
+
203
211
  get debugChildren(): readonly Component[] {
204
212
  return [this.control];
205
213
  }
@@ -16,7 +16,21 @@ export interface ImageOptions {
16
16
  * repaint replaces the placement instead of stacking a duplicate.
17
17
  */
18
18
  imageKey?: string;
19
+ /**
20
+ * Schedules a repaint once this image's off-thread SIXEL encode lands.
21
+ * Defaults to the budget's repaint; a host without a budget must pass one,
22
+ * or the image's reserved rows stay blank until an unrelated repaint.
23
+ */
24
+ requestRender?: () => void;
19
25
  }
26
+ /**
27
+ * Run `render` for rows bound for native scrollback. Those rows are never
28
+ * repainted, so an image whose off-thread SIXEL encode has not landed encodes
29
+ * synchronously instead of committing its reserved blank rows.
30
+ */
31
+ export declare function renderForScrollback<T>(render: () => T): T;
32
+ /** Widest an {@link Image} rendered at `width` columns draws; callers sizing a raster for it (`SvgFigure`) match it. */
33
+ export declare function imageMaxColumns(width: number): number;
20
34
  /** Default count of inline images kept as live graphics before older ones fall back to text. */
21
35
  export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
22
36
  /**
@@ -52,6 +66,8 @@ export declare class ImageBudget {
52
66
  get cap(): number;
53
67
  get enabled(): boolean;
54
68
  setRequestRender(requestRender: () => void): void;
69
+ /** Ask for a repaint, e.g. once an image's off-thread encode settles. */
70
+ requestRender(): void;
55
71
  setCap(cap: number): void;
56
72
  /**
57
73
  * Stable graphics id for a logical image. A non-empty `key` maps to the same
@@ -59,6 +75,13 @@ export declare class ImageBudget {
59
75
  * gets a fresh id every call.
60
76
  */
61
77
  acquireId(key?: string): number;
78
+ /**
79
+ * Retire the graphic held under `key` as soon as no frame shows it, instead
80
+ * of when the residency sweep reaches it. For an owner that replaced the
81
+ * image for good — a streaming SVG figure's earlier raster — so superseded
82
+ * revisions never push older images (scrollback included) out of the store.
83
+ */
84
+ release(key: string): void;
62
85
  /**
63
86
  * Start an alternate-buffer lifecycle. Call once per `?1049h`, before the
64
87
  * first pass of the fullscreen overlay or resize borrow that owns the buffer.
@@ -126,50 +149,8 @@ export declare class ImageBudget {
126
149
  * the image is purged from the terminal store.
127
150
  */
128
151
  registerPlacementGeometry(imageId: number, widthPx: number, heightPx: number): void;
129
- /**
130
- * Record this frame's native-scrollback commit target (the frame-row count
131
- * that is committed once the frame's writes land). Called once per rendered
132
- * frame — including frames that emit no placements — so an epoch whose rows
133
- * commit while its line is never rewritten is still flagged before the next
134
- * re-emission.
135
- */
136
- observeCommitWatermark(committedTo: number): void;
137
- /**
138
- * End the physical-row coordinate epoch after observing its final commit
139
- * watermark. Placement ids and latched archive state survive, but attachment
140
- * rows do not: the next placement emit records them in the new-width frame.
141
- */
142
- beginPlacementCoordinateEpoch(): void;
143
- /**
144
- * Resolve the placement id and geometry for a direct-placement emit whose
145
- * topmost attached cell sits at `attachTopFrameRow` — the first frame row
146
- * the placement covers, i.e. the block's first *visible* row, not its
147
- * origin (-1 when the writer has no frame-space position: alt-screen,
148
- * resize, ConPTY-truncated replays). `committedTo` is this frame's commit
149
- * target in the same frame-row space (-1 when unknown).
150
- *
151
- * Invariant: a placement id may be re-used (Kitty replace strips that id's
152
- * cells everywhere, scrollback included) only while none of the cells it
153
- * attached have entered native scrollback. The epoch — the `p=` id —
154
- * advances exactly when the archived flag says otherwise; rewrites with no
155
- * commit progression keep replacing the same id in place.
156
- */
157
- resolvePlacementEmit(imageId: number, attachTopFrameRow: number, committedTo: number): {
158
- placementId: number;
159
- widthPx: number;
160
- heightPx: number;
161
- } | null;
162
- /**
163
- * Restart every placement epoch after a destructive history clear (`CSI 3 J`
164
- * full paint). The clear destroys all placement cells — scrollback rows are
165
- * gone and the replay rewrites the viewport — so no archive remains to
166
- * protect. Reverting to epoch 1 lets the replay recreate every visible
167
- * placement after the terminal-wide cleanup.
168
- */
169
- resetPlacementEpochs(): ReadonlyArray<{
170
- imageId: number;
171
- lastEpoch: number;
172
- }>;
152
+ /** Source dimensions used by the frame writer to clip a direct placement. */
153
+ getPlacementGeometry(imageId: number): ImageDimensions | undefined;
173
154
  /**
174
155
  * Queue a one-time transmit for `imageId` on the surface the in-flight pass
175
156
  * paints. No-op if that surface already holds the data, so a repeated call
@@ -204,6 +185,7 @@ export declare class Image implements Component {
204
185
  /** Return source metadata without exposing the encoded image buffer. */
205
186
  debugState(): Record<string, unknown>;
206
187
  invalidate(): void;
188
+ releaseRenderCaches(): void;
207
189
  /**
208
190
  * A native `image` backed by a content-addressed blob; the terminal fits
209
191
  * it. Cell caps become `ch`/`lines` bounds. The inline-image budget and
@@ -1,5 +1,7 @@
1
+ import { encodeSixelAsync } from "@linxiraos/pi-natives";
1
2
  import { getKittyGraphics } from "../kitty-graphics";
2
3
  import {
4
+ encodeSixelNow,
3
5
  getCellDimensions,
4
6
  getImageDimensions,
5
7
  type ImageDimensions,
@@ -8,8 +10,7 @@ import {
8
10
  renderImage,
9
11
  TERMINAL,
10
12
  } from "../terminal-capabilities";
11
- import { registerNativeBlob } from "../native/blobs";
12
- import { node } from "../native/describe";
13
+ import { nativeImageNode } from "../native/blobs";
13
14
  import type { DescribeContext, NativeNode } from "../native/node";
14
15
  import type { Component } from "../tui";
15
16
 
@@ -29,39 +30,47 @@ export interface ImageOptions {
29
30
  * repaint replaces the placement instead of stacking a duplicate.
30
31
  */
31
32
  imageKey?: string;
33
+ /**
34
+ * Schedules a repaint once this image's off-thread SIXEL encode lands.
35
+ * Defaults to the budget's repaint; a host without a budget must pass one,
36
+ * or the image's reserved rows stay blank until an unrelated repaint.
37
+ */
38
+ requestRender?: () => void;
39
+ }
40
+
41
+ /** Renders in progress whose rows are bound for native scrollback. */
42
+ let scrollbackRenderDepth = 0;
43
+
44
+ /**
45
+ * Run `render` for rows bound for native scrollback. Those rows are never
46
+ * repainted, so an image whose off-thread SIXEL encode has not landed encodes
47
+ * synchronously instead of committing its reserved blank rows.
48
+ */
49
+ export function renderForScrollback<T>(render: () => T): T {
50
+ scrollbackRenderDepth++;
51
+ try {
52
+ return render();
53
+ } finally {
54
+ scrollbackRenderDepth--;
55
+ }
32
56
  }
33
57
 
34
58
  const EMPTY_IDS: readonly number[] = [];
35
59
  const EMPTY_TRANSMITS: readonly string[] = [];
36
- const EMPTY_STALE_EPOCHS: ReadonlyArray<{ imageId: number; lastEpoch: number }> = [];
37
60
  const SAVE_CURSOR = "\x1b7";
38
61
  const RESTORE_CURSOR = "\x1b8";
39
62
  // Direct placements reserve height with leading zero-width rows. Keep them
40
63
  // non-plain so transcript blank-edge trimming does not collapse image-only blocks.
41
64
  const RESERVED_IMAGE_ROW = "\x1b[0m";
42
65
 
66
+ /** Widest an {@link Image} rendered at `width` columns draws; callers sizing a raster for it (`SvgFigure`) match it. */
67
+ export function imageMaxColumns(width: number): number {
68
+ return Math.max(1, width - 2);
69
+ }
70
+
43
71
  /** Default count of inline images kept as live graphics before older ones fall back to text. */
44
72
  export const DEFAULT_MAX_INLINE_IMAGES = 8;
45
73
 
46
- /** Per-image direct-placement emit state tracked by {@link ImageBudget}. */
47
- interface PlacementEmitState {
48
- widthPx: number;
49
- heightPx: number;
50
- /** Current placement-id (`p=`) generation. */
51
- epoch: number;
52
- /** First frame row the current epoch's last emit attached cells to. */
53
- lastAttachTopFrameRow: number | undefined;
54
- /**
55
- * Whether any cell attached by the current epoch's last emit has entered
56
- * native scrollback. Set by {@link ImageBudget.observeCommitWatermark}
57
- * comparing each frame's raw commit target against the attach top —
58
- * era-local comparisons, so a divergence recommit that rewinds and
59
- * re-advances the ledger is detected the moment it re-crosses the attach
60
- * top, and a stale pre-rewind peak can never re-trigger.
61
- */
62
- cellsArchived: boolean;
63
- }
64
-
65
74
  /** A surface the renderer paints frames on. */
66
75
  type Surface = "screen" | "alt";
67
76
  const SURFACES: readonly Surface[] = ["screen", "alt"];
@@ -193,22 +202,10 @@ export class ImageBudget {
193
202
  * fullscreen overlay keeps its placements and is restored from cache on exit.
194
203
  */
195
204
  #liveIds: Record<Surface, Set<number>> = { screen: new Set(), alt: new Set() };
196
- /**
197
- * Per-image direct-placement emit state: source pixel geometry for the
198
- * renderer's clipped source rectangle, plus the placement-id epoch (see
199
- * {@link resolvePlacementEmit}). Entries deliberately live as long as the
200
- * terminal's own placement registry for the image — they are the ledger the
201
- * destructive-clear sweep uses to delete every registry entry an image ever
202
- * placed — and die with it on demotion purge (`d=I`) or full cleanup.
203
- */
204
- #placementState = new Map<number, PlacementEmitState>();
205
- /**
206
- * States with an un-archived live attach top — the only ones a frame's
207
- * commit watermark can affect. {@link observeCommitWatermark} runs every
208
- * rendered frame, so it scans this set (bounded by concurrently live
209
- * placements) instead of every image ever registered.
210
- */
211
- #watchedPlacements = new Set<PlacementEmitState>();
205
+ /** Source pixel dimensions for clipping direct placements at the viewport edge. */
206
+ #placementGeometry = new Map<number, ImageDimensions>();
207
+ /** Ids whose owner replaced them for good ({@link release}); retired once no frame shows them. */
208
+ #released = new Set<number>();
212
209
 
213
210
  constructor(cap: number = DEFAULT_MAX_INLINE_IMAGES, requestRender: () => void = () => {}) {
214
211
  this.#cap = normalizeCap(cap);
@@ -227,6 +224,11 @@ export class ImageBudget {
227
224
  this.#requestRender = requestRender;
228
225
  }
229
226
 
227
+ /** Ask for a repaint, e.g. once an image's off-thread encode settles. */
228
+ requestRender(): void {
229
+ this.#requestRender();
230
+ }
231
+
230
232
  setCap(cap: number): void {
231
233
  const next = normalizeCap(cap);
232
234
  if (next === this.#cap) return;
@@ -254,6 +256,17 @@ export class ImageBudget {
254
256
  return id;
255
257
  }
256
258
 
259
+ /**
260
+ * Retire the graphic held under `key` as soon as no frame shows it, instead
261
+ * of when the residency sweep reaches it. For an owner that replaced the
262
+ * image for good — a streaming SVG figure's earlier raster — so superseded
263
+ * revisions never push older images (scrollback included) out of the store.
264
+ */
265
+ release(key: string): void {
266
+ const id = this.#keyToId.get(key);
267
+ if (id !== undefined) this.#released.add(id);
268
+ }
269
+
257
270
  /**
258
271
  * Start an alternate-buffer lifecycle. Call once per `?1049h`, before the
259
272
  * first pass of the fullscreen overlay or resize borrow that owns the buffer.
@@ -375,6 +388,13 @@ export class ImageBudget {
375
388
  if (this.#passShowsLive(id)) liveIds.add(id);
376
389
  }
377
390
  const transmitted = this.#transmitted[this.#surface];
391
+ for (const id of this.#released) {
392
+ // #retire refuses while this pass or the other surface still shows it.
393
+ if (transmitted.has(id)) this.#retire(id);
394
+ if (this.#isTransmitted(id)) continue;
395
+ this.#forgetKeyForId(id);
396
+ this.#released.delete(id);
397
+ }
378
398
  if (this.#cap <= 0 || transmitted.size <= this.#cap) return;
379
399
  for (const id of transmitted) {
380
400
  if (transmitted.size <= this.#cap) break;
@@ -426,7 +446,7 @@ export class ImageBudget {
426
446
  // terminal, so cancel it instead of transmitting then purging.
427
447
  if (!this.#pendingTransmits[this.#surface].delete(imageId)) this.#purgeIds.push(imageId);
428
448
  this.#transmitted[this.#surface].delete(imageId);
429
- if (!this.#isTransmitted(imageId)) this.#deletePlacementState(imageId);
449
+ if (!this.#isTransmitted(imageId)) this.#placementGeometry.delete(imageId);
430
450
  this.#forgetKeyForId(imageId);
431
451
  return true;
432
452
  }
@@ -469,8 +489,8 @@ export class ImageBudget {
469
489
  this.#resetPurgeIds = [];
470
490
  this.#keyToId.clear();
471
491
  this.#idToKey.clear();
472
- this.#placementState.clear();
473
- this.#watchedPlacements.clear();
492
+ this.#placementGeometry.clear();
493
+ this.#released.clear();
474
494
  for (const surface of SURFACES) this.#liveIds[surface].clear();
475
495
  return [...ids];
476
496
  }
@@ -486,115 +506,18 @@ export class ImageBudget {
486
506
  * the image is purged from the terminal store.
487
507
  */
488
508
  registerPlacementGeometry(imageId: number, widthPx: number, heightPx: number): void {
489
- const state = this.#placementState.get(imageId);
490
- if (state) {
491
- state.widthPx = widthPx;
492
- state.heightPx = heightPx;
493
- return;
494
- }
495
- this.#placementState.set(imageId, {
496
- widthPx,
497
- heightPx,
498
- epoch: 1,
499
- lastAttachTopFrameRow: undefined,
500
- cellsArchived: false,
501
- });
502
- }
503
-
504
- /**
505
- * Record this frame's native-scrollback commit target (the frame-row count
506
- * that is committed once the frame's writes land). Called once per rendered
507
- * frame — including frames that emit no placements — so an epoch whose rows
508
- * commit while its line is never rewritten is still flagged before the next
509
- * re-emission.
510
- */
511
- observeCommitWatermark(committedTo: number): void {
512
- if (committedTo < 0 || this.#watchedPlacements.size === 0) return;
513
- for (const state of this.#watchedPlacements) {
514
- if (state.lastAttachTopFrameRow !== undefined && committedTo > state.lastAttachTopFrameRow) {
515
- // Latched: the flag only clears when the next emit consumes it,
516
- // so the state needs no further per-frame scans until then.
517
- state.cellsArchived = true;
518
- this.#watchedPlacements.delete(state);
519
- }
520
- }
521
- }
522
-
523
- /**
524
- * End the physical-row coordinate epoch after observing its final commit
525
- * watermark. Placement ids and latched archive state survive, but attachment
526
- * rows do not: the next placement emit records them in the new-width frame.
527
- */
528
- beginPlacementCoordinateEpoch(): void {
529
- for (const state of this.#placementState.values()) state.lastAttachTopFrameRow = undefined;
530
- this.#watchedPlacements.clear();
531
- }
532
-
533
- /**
534
- * Resolve the placement id and geometry for a direct-placement emit whose
535
- * topmost attached cell sits at `attachTopFrameRow` — the first frame row
536
- * the placement covers, i.e. the block's first *visible* row, not its
537
- * origin (-1 when the writer has no frame-space position: alt-screen,
538
- * resize, ConPTY-truncated replays). `committedTo` is this frame's commit
539
- * target in the same frame-row space (-1 when unknown).
540
- *
541
- * Invariant: a placement id may be re-used (Kitty replace strips that id's
542
- * cells everywhere, scrollback included) only while none of the cells it
543
- * attached have entered native scrollback. The epoch — the `p=` id —
544
- * advances exactly when the archived flag says otherwise; rewrites with no
545
- * commit progression keep replacing the same id in place.
546
- */
547
- resolvePlacementEmit(
548
- imageId: number,
549
- attachTopFrameRow: number,
550
- committedTo: number,
551
- ): { placementId: number; widthPx: number; heightPx: number } | null {
552
- const state = this.#placementState.get(imageId);
553
- if (!state) return null;
554
- // Frames that commit as they write (seam/full-paint chunk passes) pass
555
- // their own commit target; fold it in before deciding, so a commit that
556
- // lands in the same frame as the re-emission still advances the epoch.
557
- if (committedTo >= 0 && state.lastAttachTopFrameRow !== undefined && committedTo > state.lastAttachTopFrameRow) {
558
- state.cellsArchived = true;
559
- this.#watchedPlacements.delete(state);
560
- }
561
- if (state.cellsArchived) {
562
- state.epoch += 1;
563
- state.cellsArchived = false;
564
- state.lastAttachTopFrameRow = undefined;
565
- }
566
- if (attachTopFrameRow >= 0) {
567
- state.lastAttachTopFrameRow = attachTopFrameRow;
568
- this.#watchedPlacements.add(state);
569
- }
570
- return { placementId: state.epoch, widthPx: state.widthPx, heightPx: state.heightPx };
571
- }
572
-
573
- /**
574
- * Restart every placement epoch after a destructive history clear (`CSI 3 J`
575
- * full paint). The clear destroys all placement cells — scrollback rows are
576
- * gone and the replay rewrites the viewport — so no archive remains to
577
- * protect. Reverting to epoch 1 lets the replay recreate every visible
578
- * placement after the terminal-wide cleanup.
579
- */
580
- resetPlacementEpochs(): ReadonlyArray<{ imageId: number; lastEpoch: number }> {
581
- let stale: Array<{ imageId: number; lastEpoch: number }> | undefined;
582
- for (const [imageId, state] of this.#placementState) {
583
- stale ??= [];
584
- stale.push({ imageId, lastEpoch: state.epoch });
585
- state.epoch = 1;
586
- state.lastAttachTopFrameRow = undefined;
587
- state.cellsArchived = false;
509
+ const geometry = this.#placementGeometry.get(imageId);
510
+ if (geometry) {
511
+ geometry.widthPx = widthPx;
512
+ geometry.heightPx = heightPx;
513
+ } else {
514
+ this.#placementGeometry.set(imageId, { widthPx, heightPx });
588
515
  }
589
- this.#watchedPlacements.clear();
590
- return stale ?? EMPTY_STALE_EPOCHS;
591
516
  }
592
517
 
593
- #deletePlacementState(imageId: number): void {
594
- const state = this.#placementState.get(imageId);
595
- if (!state) return;
596
- this.#watchedPlacements.delete(state);
597
- this.#placementState.delete(imageId);
518
+ /** Source dimensions used by the frame writer to clip a direct placement. */
519
+ getPlacementGeometry(imageId: number): ImageDimensions | undefined {
520
+ return this.#placementGeometry.get(imageId);
598
521
  }
599
522
 
600
523
  /**
@@ -735,6 +658,8 @@ export class Image implements Component {
735
658
  // (its rows may already be committed to native scrollback).
736
659
  #renderedGraphicRows = 0;
737
660
  #native?: NativeNode;
661
+ /** Newest SIXEL encode: its target size and, once settled, the sequence (`null` on failure). */
662
+ #sixel?: { widthPx: number; heightPx: number; sequence?: string | null };
738
663
 
739
664
  constructor(
740
665
  base64Data: string,
@@ -768,6 +693,43 @@ export class Image implements Component {
768
693
  this.#cachedWidth = undefined;
769
694
  }
770
695
 
696
+ releaseRenderCaches(): void {
697
+ this.#cachedLines = undefined;
698
+ this.#cachedWidth = undefined;
699
+ }
700
+
701
+ /**
702
+ * SIXEL sequence for a target size. A new size starts the encode off the JS
703
+ * thread and answers `undefined` until it settles; the settled encode
704
+ * invalidates the cached lines and requests a repaint. A render bound for
705
+ * scrollback cannot wait, so it encodes synchronously.
706
+ */
707
+ #sixelSequence(widthPx: number, heightPx: number): string | null | undefined {
708
+ const current = this.#sixel;
709
+ const sameSize = current !== undefined && current.widthPx === widthPx && current.heightPx === heightPx;
710
+ if (sameSize && current.sequence !== undefined) return current.sequence;
711
+ if (scrollbackRenderDepth > 0) {
712
+ // Replacing the entry makes a still-pending async encode settle as stale.
713
+ const sequence = encodeSixelNow(this.#base64Data, widthPx, heightPx);
714
+ this.#sixel = { widthPx, heightPx, sequence };
715
+ return sequence;
716
+ }
717
+ if (sameSize) return undefined;
718
+ const request: { widthPx: number; heightPx: number; sequence?: string | null } = { widthPx, heightPx };
719
+ this.#sixel = request;
720
+ const settle = (sequence: string | null): void => {
721
+ request.sequence = sequence;
722
+ if (this.#sixel !== request) return;
723
+ this.invalidate();
724
+ if (this.#options.requestRender) this.#options.requestRender();
725
+ else this.#budget?.requestRender();
726
+ };
727
+ encodeSixelAsync(new Uint8Array(Buffer.from(this.#base64Data, "base64")), widthPx, heightPx).then(settle, () =>
728
+ settle(null),
729
+ );
730
+ return undefined;
731
+ }
732
+
771
733
  /**
772
734
  * A native `image` backed by a content-addressed blob; the terminal fits
773
735
  * it. Cell caps become `ch`/`lines` bounds. The inline-image budget and
@@ -775,11 +737,9 @@ export class Image implements Component {
775
737
  */
776
738
  describe(_cx: DescribeContext): NativeNode {
777
739
  if (this.#native) return this.#native;
778
- const blob = registerNativeBlob(Buffer.from(this.#base64Data, "base64"), this.#mimeType);
779
740
  const maxW = this.#options.maxWidthCells;
780
741
  const maxH = this.#options.maxHeightCells;
781
- this.#native = node("image", {
782
- blob,
742
+ this.#native = nativeImageNode(Buffer.from(this.#base64Data, "base64"), this.#mimeType, {
783
743
  alt: imageFallback(this.#mimeType, this.#dimensions, this.#options.filename),
784
744
  w: this.#dimensions.widthPx,
785
745
  h: this.#dimensions.heightPx,
@@ -820,13 +780,15 @@ export class Image implements Component {
820
780
  this.#cachedCellWidthPx === cellDimensions.widthPx &&
821
781
  this.#cachedCellHeightPx === cellDimensions.heightPx &&
822
782
  this.#cachedKittyUnicodePlaceholders === kittyUnicodePlaceholders &&
823
- (!transmitsSeparately || this.#budget?.shouldTransmit(imageId) !== true)
783
+ (!transmitsSeparately || this.#budget?.shouldTransmit(imageId) !== true) &&
784
+ // Cached rows reserved for a pending SIXEL encode must not reach scrollback.
785
+ !(scrollbackRenderDepth > 0 && this.#sixel !== undefined && this.#sixel.sequence === undefined)
824
786
  ) {
825
787
  return this.#cachedLines;
826
788
  }
827
789
 
828
790
  const cap = this.#options.maxWidthCells;
829
- const maxWidth = cap != null && cap > 0 ? Math.min(width - 2, cap) : width - 2;
791
+ const maxWidth = cap != null && cap > 0 ? Math.min(imageMaxColumns(width), cap) : imageMaxColumns(width);
830
792
 
831
793
  let lines: string[];
832
794
 
@@ -839,6 +801,7 @@ export class Image implements Component {
839
801
  maxHeightCells: this.#options.maxHeightCells,
840
802
  imageId: this.#imageId,
841
803
  includeTransmit: needsTransmit,
804
+ sixel: (widthPx, heightPx) => this.#sixelSequence(widthPx, heightPx),
842
805
  });
843
806
 
844
807
  if (result?.transmit && this.#imageId != null && this.#budget !== undefined) {
@@ -28,6 +28,7 @@ export declare class Input implements Component, Focusable {
28
28
  setValue(value: string): void;
29
29
  setUseTerminalCursor(useTerminalCursor: boolean): void;
30
30
  getUseTerminalCursor(): boolean;
31
+ capturesInput(data: string): boolean;
31
32
  /**
32
33
  * Apply one key: the editor's text bindings (motion, deletion, kill ring,
33
34
  * undo), pastes and printable text. Returns whether the key was the
@@ -39,7 +40,7 @@ export declare class Input implements Component, Focusable {
39
40
  /** Apply terminal paste semantics to text from non-bracketed paste transports
40
41
  * (e.g. kitty's OSC 5522 enhanced clipboard read). Mirrors `Editor.pasteText`. */
41
42
  pasteText(text: string): void;
42
- /** Terminal-side selection edits on the `input` node (see {@link applyHostEdit}). */
43
+ /** Terminal-side selection edits and undo on the `input` node (see {@link applyHostEdit}). */
43
44
  handleNativeEvent(event: NativeUiEvent): void;
44
45
  /**
45
46
  * Apply an edit the terminal made over its own selection (TSP `edit`):
@@ -1,6 +1,6 @@
1
1
  import { BracketedPasteHandler, decodeReencodedPasteControls } from "../bracketed-paste";
2
- import { getKeybindings } from "../keybindings";
3
- import { extractPrintableText, matchesKey } from "../keys";
2
+ import { canonicalKeyId, getKeybindings } from "../keybindings";
3
+ import { extractPrintableText, parseKey } from "../keys";
4
4
  import { KillRing } from "../kill-ring";
5
5
  import type { TspInputProps } from "@linxiraos/pi-wire";
6
6
  import { node } from "../native/describe";
@@ -14,7 +14,7 @@ import {
14
14
  type NativeUiEvent,
15
15
  resolveTextEdit,
16
16
  } from "../native/node";
17
- import { SpaceHoldGesture } from "../space-hold";
17
+ import { getSpaceHoldText, SpaceHoldGesture } from "../space-hold";
18
18
  import { type Component, CURSOR_MARKER, type Focusable } from "../tui";
19
19
  import { cursorColumnWindow } from "./scroll-viewport";
20
20
  import {
@@ -134,6 +134,10 @@ export class Input implements Component, Focusable {
134
134
  return this.#useTerminalCursor;
135
135
  }
136
136
 
137
+ capturesInput(data: string): boolean {
138
+ return this.spaceHold.shouldRoute(data);
139
+ }
140
+
137
141
  /**
138
142
  * Apply one key: the editor's text bindings (motion, deletion, kill ring,
139
143
  * undo), pastes and printable text. Returns whether the key was the
@@ -154,10 +158,14 @@ export class Input implements Component, Focusable {
154
158
  return true;
155
159
  }
156
160
 
157
- // Space-hold push-to-talk: a sustained space bar starts/stops STT instead of typing spaces.
158
- switch (this.spaceHold.process(matchesKey(data, "space"))) {
161
+ // Reserve configured keys before text bindings or submit/delete handlers can consume them.
162
+ const parsedKey = parseKey(data);
163
+ const canonical = parsedKey !== undefined ? canonicalKeyId(parsedKey) : undefined;
164
+ const spaceHoldText = getSpaceHoldText(data, canonical);
165
+ switch (this.spaceHold.process(canonical, spaceHoldText?.length ?? 0)) {
159
166
  case "type":
160
- this.#insertCharacter(" ");
167
+ this.#insertCharacter(spaceHoldText!);
168
+ this.spaceHold.recordTyped(spaceHoldText!.length);
161
169
  return true;
162
170
  case "swallow":
163
171
  return true;
@@ -284,9 +292,10 @@ export class Input implements Component, Focusable {
284
292
  this.#handlePaste(text);
285
293
  }
286
294
 
287
- /** Terminal-side selection edits on the `input` node (see {@link applyHostEdit}). */
295
+ /** Terminal-side selection edits and undo on the `input` node (see {@link applyHostEdit}). */
288
296
  handleNativeEvent(event: NativeUiEvent): void {
289
297
  if (event.type === "edit") this.applyHostEdit(event);
298
+ else if (event.type === "undo") this.#undo();
290
299
  }
291
300
 
292
301
  /**
@@ -582,6 +591,7 @@ export class Input implements Component, Focusable {
582
591
  const props: TspInputProps = {
583
592
  text: value,
584
593
  cursor,
594
+ sendable: false,
585
595
  prompt: prompt || undefined,
586
596
  placeholder: this.placeholder,
587
597
  };
@@ -21,6 +21,7 @@ export declare class KeyValueList implements Component {
21
21
  constructor(rows: readonly KeyValueRow[], options: KeyValueListOptions);
22
22
  setRows(rows: readonly KeyValueRow[]): boolean;
23
23
  invalidate(): void;
24
+ releaseRenderCaches(): void;
24
25
  /** An aligned `kv` grid; label width and value wrapping are the terminal's. */
25
26
  describe(_cx: DescribeContext): NativeNode;
26
27
  render(width: number): readonly string[];
@@ -54,6 +54,10 @@ export class KeyValueList implements Component {
54
54
  this.#native = undefined;
55
55
  }
56
56
 
57
+ releaseRenderCaches(): void {
58
+ this.#cache = undefined;
59
+ }
60
+
57
61
  /** An aligned `kv` grid; label width and value wrapping are the terminal's. */
58
62
  describe(_cx: DescribeContext): NativeNode {
59
63
  this.#native ??= node("kv", {
@@ -48,6 +48,7 @@ export declare class Row implements Component, MouseRoutable {
48
48
  setDecorations(prefix: LayoutDecoration | undefined, gap: LayoutDecoration | undefined, suffix: LayoutDecoration | undefined): void;
49
49
  setIgnoreTight(ignore: boolean): this;
50
50
  invalidate(): void;
51
+ releaseRenderCaches(): void;
51
52
  dispose(): void;
52
53
  /**
53
54
  * A flex `row`: each child sits in a slot carrying its width bounds
@@ -168,6 +168,13 @@ export class Row implements Component, MouseRoutable {
168
168
  }
169
169
  }
170
170
 
171
+ releaseRenderCaches(): void {
172
+ this.#memo = undefined;
173
+ for (const child of uniqueLayoutComponents(this.#children.map(item => item.content))) {
174
+ child.releaseRenderCaches?.();
175
+ }
176
+ }
177
+
171
178
  dispose(): void {
172
179
  for (const child of uniqueLayoutComponents(this.#children.map(item => item.content))) {
173
180
  child.dispose?.();
@@ -43,6 +43,7 @@ export declare class Stack implements Component, MouseRoutable {
43
43
  setHeight(height: number | undefined): void;
44
44
  setIgnoreTight(ignore: boolean): this;
45
45
  invalidate(): void;
46
+ releaseRenderCaches(): void;
46
47
  dispose(): void;
47
48
  /**
48
49
  * A `col` of slots carrying each child's height bounds (fixed → min = max
@@ -140,6 +140,13 @@ export class Stack implements Component, MouseRoutable {
140
140
  }
141
141
  }
142
142
 
143
+ releaseRenderCaches(): void {
144
+ this.#memo = undefined;
145
+ for (const child of uniqueLayoutComponents(this.#children.map(item => item.content))) {
146
+ child.releaseRenderCaches?.();
147
+ }
148
+ }
149
+
143
150
  dispose(): void {
144
151
  for (const child of uniqueLayoutComponents(this.#children.map(item => item.content))) {
145
152
  child.dispose?.();