@linxiraos/pi-tui 1.1.27 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/CHANGELOG.md +50 -461
  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 +2 -7
  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 +19 -42
  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/splash.d.ts +1 -1
  95. package/dist/types/setup/scenes/types.d.ts +1 -0
  96. package/dist/types/status-line/component.d.ts +3 -0
  97. package/dist/types/status-line/host.d.ts +4 -1
  98. package/dist/types/status-line/segments.d.ts +2 -0
  99. package/dist/types/status-line/types.d.ts +14 -3
  100. package/dist/types/terminal-capabilities.d.ts +27 -35
  101. package/dist/types/terminal-multiplexer.d.ts +127 -14
  102. package/dist/types/terminal.d.ts +22 -6
  103. package/dist/types/theme/active-symbols.d.ts +1 -1
  104. package/dist/types/theme/schema.d.ts +11 -2
  105. package/dist/types/theme/symbols.d.ts +1 -1
  106. package/dist/types/theme/theme-class.d.ts +7 -0
  107. package/dist/types/theme/theme.d.ts +1 -1
  108. package/dist/types/theme/tui-adapters.d.ts +3 -0
  109. package/dist/types/tmux.d.ts +11 -4
  110. package/dist/types/tools/daemon.d.ts +1 -1
  111. package/dist/types/tools/eval.d.ts +22 -3
  112. package/dist/types/tools/glob.d.ts +4 -0
  113. package/dist/types/tools/native-view.d.ts +47 -0
  114. package/dist/types/tools/output-meta.d.ts +11 -1
  115. package/dist/types/tools/renderer.d.ts +37 -2
  116. package/dist/types/tools/streaming-output.d.ts +20 -1
  117. package/dist/types/tools/task.d.ts +0 -4
  118. package/dist/types/tools/write.d.ts +9 -2
  119. package/dist/types/tools/yield.d.ts +22 -0
  120. package/dist/types/tui.d.ts +31 -0
  121. package/dist/types/utils.d.ts +6 -0
  122. package/dist/types/vim.d.ts +19 -5
  123. package/package.json +13 -9
  124. package/src/app-keybindings.d.ts +1 -1
  125. package/src/app-keybindings.ts +1 -1
  126. package/src/apps/autoresearch-dashboard.ts +7 -3
  127. package/src/apps/cleanse-board.ts +2 -2
  128. package/src/apps/cleanse-picker.ts +1 -1
  129. package/src/apps/debug/log-viewer.ts +188 -72
  130. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  131. package/src/apps/debug/raw-sse.ts +39 -5
  132. package/src/apps/git/diff-pane.ts +26 -5
  133. package/src/apps/git/git-tui.ts +2 -2
  134. package/src/apps/git/help.ts +29 -20
  135. package/src/apps/if-bench-board.ts +2 -2
  136. package/src/apps/ps-top.ts +2 -2
  137. package/src/apps/setup-model-picker.ts +1 -1
  138. package/src/autocomplete.d.ts +5 -2
  139. package/src/autocomplete.ts +67 -43
  140. package/src/charts/chart-plan.d.ts +674 -0
  141. package/src/charts/chart-plan.ts +5081 -0
  142. package/src/charts/chart-svg.d.ts +59 -0
  143. package/src/charts/chart-svg.ts +4657 -0
  144. package/src/charts/table-data.d.ts +175 -0
  145. package/src/charts/table-data.ts +1478 -0
  146. package/src/chat/advisor-message.ts +11 -0
  147. package/src/chat/assistant-message.d.ts +5 -0
  148. package/src/chat/assistant-message.ts +219 -50
  149. package/src/chat/chat-transcript-builder.d.ts +2 -0
  150. package/src/chat/chat-transcript-builder.ts +5 -3
  151. package/src/chat/compaction-summary-message.d.ts +1 -0
  152. package/src/chat/compaction-summary-message.ts +8 -0
  153. package/src/chat/execution-shared.d.ts +6 -5
  154. package/src/chat/execution-shared.ts +42 -25
  155. package/src/chat/fence-figure.d.ts +42 -0
  156. package/src/chat/fence-figure.ts +80 -0
  157. package/src/chat/figure-markdown.d.ts +42 -0
  158. package/src/chat/figure-markdown.ts +174 -0
  159. package/src/chat/messages.d.ts +1 -1
  160. package/src/chat/messages.ts +1 -1
  161. package/src/chat/read-tool-group.ts +6 -4
  162. package/src/chat/skill-message.ts +6 -0
  163. package/src/chat/svg-figure.d.ts +33 -0
  164. package/src/chat/svg-figure.ts +399 -0
  165. package/src/chat/svg-source.d.ts +49 -0
  166. package/src/chat/svg-source.ts +188 -0
  167. package/src/chat/table-chart.d.ts +89 -0
  168. package/src/chat/table-chart.ts +294 -0
  169. package/src/chat/tool-execution.d.ts +2 -0
  170. package/src/chat/tool-execution.ts +80 -6
  171. package/src/chat/user-message.d.ts +15 -2
  172. package/src/chat/user-message.ts +55 -3
  173. package/src/chrome/shared.ts +53 -0
  174. package/src/chrome/transcript-container.d.ts +6 -0
  175. package/src/chrome/transcript-container.ts +93 -13
  176. package/src/components/box.d.ts +1 -0
  177. package/src/components/box.ts +5 -0
  178. package/src/components/disclosure.d.ts +1 -0
  179. package/src/components/disclosure.ts +8 -0
  180. package/src/components/editor.d.ts +15 -4
  181. package/src/components/editor.ts +294 -46
  182. package/src/components/form.d.ts +1 -0
  183. package/src/components/form.ts +8 -0
  184. package/src/components/image.d.ts +12 -44
  185. package/src/components/image.ts +47 -151
  186. package/src/components/key-value-list.d.ts +1 -0
  187. package/src/components/key-value-list.ts +4 -0
  188. package/src/components/layout/row.d.ts +1 -0
  189. package/src/components/layout/row.ts +7 -0
  190. package/src/components/layout/stack.d.ts +1 -0
  191. package/src/components/layout/stack.ts +7 -0
  192. package/src/components/markdown.d.ts +36 -3
  193. package/src/components/markdown.ts +433 -318
  194. package/src/components/scroll-view.d.ts +1 -0
  195. package/src/components/scroll-view.ts +5 -0
  196. package/src/components/section.d.ts +1 -0
  197. package/src/components/section.ts +5 -0
  198. package/src/components/settings-list.ts +2 -0
  199. package/src/components/table.d.ts +1 -0
  200. package/src/components/table.ts +4 -0
  201. package/src/components/text.d.ts +1 -0
  202. package/src/components/text.ts +7 -0
  203. package/src/components/tree-view.d.ts +7 -0
  204. package/src/components/tree-view.ts +38 -8
  205. package/src/components/truncated-text.d.ts +1 -0
  206. package/src/components/truncated-text.ts +5 -0
  207. package/src/desktop-notify.ts +1 -1
  208. package/src/fuzzy.d.ts +29 -1
  209. package/src/fuzzy.ts +115 -30
  210. package/src/key-hint-format.d.ts +1 -1
  211. package/src/key-hint-format.ts +1 -1
  212. package/src/keybinding-matchers.d.ts +3 -0
  213. package/src/keybinding-matchers.ts +6 -2
  214. package/src/keybindings.d.ts +1 -1
  215. package/src/keybindings.ts +1 -1
  216. package/src/kitty-graphics.d.ts +2 -2
  217. package/src/kitty-graphics.ts +4 -4
  218. package/src/loop-watchdog.d.ts +8 -7
  219. package/src/loop-watchdog.ts +18 -13
  220. package/src/multiplexers/cmux.d.ts +16 -0
  221. package/src/multiplexers/cmux.ts +29 -0
  222. package/src/multiplexers/herdr.d.ts +26 -0
  223. package/src/multiplexers/herdr.ts +64 -0
  224. package/src/multiplexers/notify.d.ts +13 -0
  225. package/src/multiplexers/notify.ts +24 -0
  226. package/src/multiplexers/orca.d.ts +12 -0
  227. package/src/multiplexers/orca.ts +16 -0
  228. package/src/multiplexers/screen.d.ts +9 -0
  229. package/src/multiplexers/screen.ts +15 -0
  230. package/src/multiplexers/tmux.d.ts +29 -0
  231. package/src/multiplexers/tmux.ts +40 -0
  232. package/src/multiplexers/types.d.ts +100 -0
  233. package/src/multiplexers/types.ts +110 -0
  234. package/src/multiplexers/wmux.d.ts +12 -0
  235. package/src/multiplexers/wmux.ts +16 -0
  236. package/src/multiplexers/zellij.d.ts +12 -0
  237. package/src/multiplexers/zellij.ts +24 -0
  238. package/src/native/backend.d.ts +3 -2
  239. package/src/native/backend.ts +174 -18
  240. package/src/native/blobs.d.ts +7 -3
  241. package/src/native/blobs.ts +47 -12
  242. package/src/native/encode.d.ts +3 -4
  243. package/src/native/encode.ts +58 -31
  244. package/src/native/overlay.d.ts +5 -0
  245. package/src/native/overlay.ts +13 -0
  246. package/src/native/reconcile.ts +4 -0
  247. package/src/overlays/agent-hub-projection.d.ts +6 -5
  248. package/src/overlays/agent-hub-projection.ts +124 -13
  249. package/src/overlays/agent-hub-types.d.ts +9 -0
  250. package/src/overlays/agent-hub-types.ts +6 -0
  251. package/src/overlays/agent-hub.ts +46 -31
  252. package/src/overlays/agent-transcript-viewer.ts +91 -23
  253. package/src/overlays/agents-hub.ts +28 -8
  254. package/src/overlays/annotation-overlay.ts +293 -90
  255. package/src/overlays/annotation-types.ts +2 -0
  256. package/src/overlays/ask-dialog.ts +7 -10
  257. package/src/overlays/btw-history-panel.ts +12 -1
  258. package/src/overlays/copy-selector.ts +30 -8
  259. package/src/overlays/extensions/state-manager.ts +52 -12
  260. package/src/overlays/history-search.ts +100 -7
  261. package/src/overlays/hook-selector.ts +12 -5
  262. package/src/overlays/jobs-panel.ts +28 -46
  263. package/src/overlays/model-browser.ts +341 -110
  264. package/src/overlays/model-hub.ts +300 -49
  265. package/src/overlays/move-overlay.ts +28 -14
  266. package/src/overlays/oauth-selector.ts +54 -70
  267. package/src/overlays/plan-review-overlay.ts +194 -70
  268. package/src/overlays/plugin-settings.ts +11 -5
  269. package/src/overlays/report-panel.ts +2 -2
  270. package/src/overlays/rewind-selector.ts +86 -17
  271. package/src/overlays/session-observer-registry.d.ts +2 -0
  272. package/src/overlays/session-observer-registry.ts +10 -2
  273. package/src/overlays/session-selector.ts +64 -26
  274. package/src/overlays/settings-defs.ts +4 -3
  275. package/src/overlays/settings-selector.ts +33 -8
  276. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  277. package/src/overlays/stats-notice.ts +1 -1
  278. package/src/overlays/tree-selector.ts +41 -20
  279. package/src/overlays/usage-dashboard.ts +100 -50
  280. package/src/overlays/usage-display.ts +10 -4
  281. package/src/prompt/attachment-chips.ts +15 -11
  282. package/src/prompt/composer-attachments.ts +6 -4
  283. package/src/prompt/composer-cache.ts +4 -6
  284. package/src/prompt/composer.ts +6 -37
  285. package/src/prompt/custom-editor.ts +54 -26
  286. package/src/prompt/image-source.ts +2 -1
  287. package/src/prompt/interactive-context-helpers.ts +30 -18
  288. package/src/prompt/macos-spelling.ts +7 -0
  289. package/src/prompt/magic-keywords.ts +11 -2
  290. package/src/prompt/model-mention-autocomplete.ts +14 -14
  291. package/src/prompt/tips.txt +6 -5
  292. package/src/prompt/welcome.ts +235 -411
  293. package/src/prompt/word-completion.ts +38 -5
  294. package/src/render/composite.d.ts +2 -0
  295. package/src/render/composite.ts +64 -0
  296. package/src/render/output-pane.d.ts +1 -0
  297. package/src/render/output-pane.ts +6 -0
  298. package/src/render/render-utils.d.ts +1 -0
  299. package/src/render/render-utils.ts +11 -0
  300. package/src/render/tool-card.d.ts +6 -0
  301. package/src/render/tool-card.ts +15 -0
  302. package/src/render/width-aware-text.d.ts +2 -0
  303. package/src/render/width-aware-text.ts +5 -0
  304. package/src/setup/scenes/model.ts +2 -1
  305. package/src/setup/scenes/splash.ts +116 -53
  306. package/src/setup/scenes/types.ts +1 -0
  307. package/src/status-line/component.ts +292 -78
  308. package/src/status-line/host.ts +4 -1
  309. package/src/status-line/segments.ts +40 -22
  310. package/src/status-line/types.ts +15 -3
  311. package/src/terminal-capabilities.d.ts +27 -35
  312. package/src/terminal-capabilities.ts +75 -189
  313. package/src/terminal-multiplexer.d.ts +127 -14
  314. package/src/terminal-multiplexer.ts +112 -33
  315. package/src/terminal.d.ts +22 -6
  316. package/src/terminal.ts +139 -37
  317. package/src/theme/active-symbols.d.ts +1 -1
  318. package/src/theme/active-symbols.ts +1 -1
  319. package/src/theme/schema-validation.ts +1 -0
  320. package/src/theme/schema.d.ts +11 -2
  321. package/src/theme/schema.ts +14 -1
  322. package/src/theme/session-color.ts +1 -7
  323. package/src/theme/symbols.d.ts +1 -1
  324. package/src/theme/symbols.ts +6 -1
  325. package/src/theme/theme-class.d.ts +7 -0
  326. package/src/theme/theme-class.ts +26 -1
  327. package/src/theme/theme-schema.json +5 -1
  328. package/src/theme/theme.d.ts +1 -1
  329. package/src/theme/theme.ts +8 -4
  330. package/src/theme/tui-adapters.d.ts +3 -0
  331. package/src/theme/tui-adapters.ts +68 -0
  332. package/src/tmux.d.ts +11 -4
  333. package/src/tmux.ts +47 -11
  334. package/src/tools/ast-edit.ts +59 -35
  335. package/src/tools/ast-grep.ts +35 -16
  336. package/src/tools/bash.ts +92 -42
  337. package/src/tools/daemon.d.ts +1 -1
  338. package/src/tools/daemon.ts +1 -1
  339. package/src/tools/default-renderer.ts +35 -19
  340. package/src/tools/edit.ts +142 -120
  341. package/src/tools/eval-format/index.ts +28 -2
  342. package/src/tools/eval-format/javascript.ts +7 -5
  343. package/src/tools/eval.d.ts +22 -3
  344. package/src/tools/eval.ts +364 -208
  345. package/src/tools/fetch.ts +5 -6
  346. package/src/tools/glob.d.ts +4 -0
  347. package/src/tools/glob.ts +33 -65
  348. package/src/tools/grep.ts +81 -48
  349. package/src/tools/index.ts +2 -0
  350. package/src/tools/lsp.ts +81 -59
  351. package/src/tools/mcp.ts +111 -68
  352. package/src/tools/native-view.d.ts +47 -0
  353. package/src/tools/native-view.ts +102 -1
  354. package/src/tools/output-meta.d.ts +11 -1
  355. package/src/tools/output-meta.ts +23 -3
  356. package/src/tools/read.ts +7 -5
  357. package/src/tools/renderer.d.ts +37 -2
  358. package/src/tools/renderer.ts +42 -2
  359. package/src/tools/streaming-output.d.ts +20 -1
  360. package/src/tools/streaming-output.ts +149 -50
  361. package/src/tools/task.d.ts +0 -4
  362. package/src/tools/task.ts +112 -59
  363. package/src/tools/todo.ts +119 -30
  364. package/src/tools/wait.ts +34 -24
  365. package/src/tools/web-search.ts +1 -1
  366. package/src/tools/write.d.ts +9 -2
  367. package/src/tools/write.ts +147 -95
  368. package/src/tools/yield.d.ts +22 -0
  369. package/src/tools/yield.ts +225 -0
  370. package/src/tui.d.ts +31 -0
  371. package/src/tui.ts +309 -160
  372. package/src/utils.d.ts +6 -0
  373. package/src/utils.ts +19 -0
  374. package/src/vim.d.ts +19 -5
  375. package/src/vim.ts +333 -18
  376. package/src/windows-input-mode.ts +8 -0
package/src/tools/todo.ts CHANGED
@@ -65,11 +65,31 @@ export interface TodoToolDetails {
65
65
  * that would collide across unrelated todos. */
66
66
  const TODO_DESCRIPTION_MIN_OVERLAP = 6;
67
67
 
68
+ /** Bound on {@link normalizedTodoCache}; cleared wholesale when exceeded. */
69
+ const NORMALIZED_TODO_CACHE_LIMIT = 1024;
70
+ const normalizedTodoCache = new Map<string, string>();
71
+
68
72
  function normalizeForTodoMatch(value: string): string {
69
- return value
73
+ let normalized = normalizedTodoCache.get(value);
74
+ if (normalized !== undefined) return normalized;
75
+ normalized = value
70
76
  .toLowerCase()
71
77
  .replace(/[^\p{L}\p{N}]+/gu, " ")
72
78
  .trim();
79
+ if (normalizedTodoCache.size >= NORMALIZED_TODO_CACHE_LIMIT) normalizedTodoCache.clear();
80
+ normalizedTodoCache.set(value, normalized);
81
+ return normalized;
82
+ }
83
+
84
+ /** Match a pre-normalized target against pre-normalized, non-empty candidates. */
85
+ function matchesNormalizedCandidates(target: string, candidates: readonly string[]): boolean {
86
+ if (!target) return false;
87
+ for (const candidate of candidates) {
88
+ if (target === candidate) return true;
89
+ if (target.length >= TODO_DESCRIPTION_MIN_OVERLAP && candidate.includes(target)) return true;
90
+ if (candidate.length >= TODO_DESCRIPTION_MIN_OVERLAP && target.includes(candidate)) return true;
91
+ }
92
+ return false;
73
93
  }
74
94
 
75
95
  /**
@@ -88,14 +108,12 @@ function normalizeForTodoMatch(value: string): string {
88
108
  export function todoMatchesAnyDescription(content: string, descriptions: readonly string[]): boolean {
89
109
  const target = normalizeForTodoMatch(content);
90
110
  if (!target) return false;
111
+ const candidates: string[] = [];
91
112
  for (const desc of descriptions) {
92
113
  const candidate = normalizeForTodoMatch(desc);
93
- if (!candidate) continue;
94
- if (target === candidate) return true;
95
- if (target.length >= TODO_DESCRIPTION_MIN_OVERLAP && candidate.includes(target)) return true;
96
- if (candidate.length >= TODO_DESCRIPTION_MIN_OVERLAP && target.includes(candidate)) return true;
114
+ if (candidate) candidates.push(candidate);
97
115
  }
98
- return false;
116
+ return matchesNormalizedCandidates(target, candidates);
99
117
  }
100
118
 
101
119
  /** Whether a todo is settled: completed or deliberately abandoned. Shared so
@@ -144,13 +162,16 @@ const COLLAPSED_CLOSED_CONTEXT = 1;
144
162
  * 3. When active tasks alone exceed `cap`, only the first `cap` active tasks are
145
163
  * shown and the summary counts the hidden *active* todos, never replacing
146
164
  * them with unrelated pending rows.
165
+ *
166
+ * The summary occupies a row, so a summary that would hide exactly one task is
167
+ * replaced by that task: the selection spends at most `cap + 1` rows.
147
168
  */
148
169
  function selectWithinCap<T extends { status: TodoStatus }>(
149
170
  base: T[],
150
171
  isMatched: (task: T) => boolean,
151
172
  cap: number,
152
173
  ): CollapsedTodoSelection<T> {
153
- if (base.length <= cap) return { items: base, summary: "" };
174
+ if (base.length <= cap + 1) return { items: base, summary: "" };
154
175
 
155
176
  const active = base.filter(task => isActiveTodo(task, isMatched));
156
177
  // Only when active work strictly exceeds the cap do we drop pending rows and
@@ -311,24 +332,37 @@ function strikethroughText(text: string): string {
311
332
  return `${STRIKE_START}${text}${STRIKE_END}`;
312
333
  }
313
334
 
314
- function partialStrikethrough(text: string, visibleChars: number): string {
335
+ /** Display label for a todo row, with its code points split once for the strike animation. */
336
+ interface TodoLabel {
337
+ text: string;
338
+ chars?: string[];
339
+ }
340
+
341
+ function labelChars(label: TodoLabel): string[] {
342
+ label.chars ??= [...label.text];
343
+ return label.chars;
344
+ }
345
+
346
+ function partialStrikethrough(label: TodoLabel, visibleChars: number): string {
347
+ const text = label.text;
315
348
  if (visibleChars <= 0) return text;
316
- const chars = [...text];
349
+ const chars = labelChars(label);
317
350
  if (visibleChars >= chars.length) return strikethroughText(text);
318
351
  return `${strikethroughText(chars.slice(0, visibleChars).join(""))}${chars.slice(visibleChars).join("")}`;
319
352
  }
320
353
 
321
- function strikeRevealCount(text: string, frame: number | undefined): number | undefined {
354
+ function strikeRevealCount(label: TodoLabel, frame: number | undefined): number | undefined {
322
355
  if (frame === undefined) return undefined;
323
356
  if (frame <= TODO_STRIKE_HOLD_FRAMES) return 0;
324
- const chars = [...text];
325
- if (chars.length === 0) return undefined;
357
+ const count = labelChars(label).length;
358
+ if (count === 0) return undefined;
326
359
  const revealFrame = Math.min(frame - TODO_STRIKE_HOLD_FRAMES, TODO_STRIKE_REVEAL_FRAMES);
327
- return Math.ceil((chars.length * revealFrame) / TODO_STRIKE_REVEAL_FRAMES);
360
+ return Math.ceil((count * revealFrame) / TODO_STRIKE_REVEAL_FRAMES);
328
361
  }
329
362
 
330
363
  function formatTodoLine(
331
364
  item: TodoItem,
365
+ label: TodoLabel,
332
366
  uiTheme: Theme,
333
367
  prefix: string,
334
368
  completionKeys: Set<string>,
@@ -336,31 +370,27 @@ function formatTodoLine(
336
370
  matched = false,
337
371
  ): string {
338
372
  const checkbox = uiTheme.checkbox;
339
- // Sanitize only for display. A mirrored Cursor snapshot carries provider text
340
- // verbatim, and a label holding ANSI/C0 sequences would otherwise rewrite the
341
- // terminal every time the list renders or replays. `item.content` stays raw
342
- // everywhere else: it is the identity key the local list is looked up by
343
- // (`findTaskByContent`) and what gets persisted.
344
- const label = forDisplay(item.content);
373
+ // `label` is the display-sanitized content (see `forDisplay`); `item.content`
374
+ // stays raw everywhere else as the identity key and persisted value.
375
+ const text = label.text;
345
376
  switch (item.status) {
346
377
  case "completed": {
347
378
  const revealCount = completionKeys.has(item.content) ? strikeRevealCount(label, frame) : undefined;
348
- const content =
349
- revealCount === undefined ? strikethroughText(label) : partialStrikethrough(label, revealCount);
379
+ const content = revealCount === undefined ? strikethroughText(text) : partialStrikethrough(label, revealCount);
350
380
  return uiTheme.fg("success", `${prefix}${checkbox.checked} ${content}`);
351
381
  }
352
382
  case "in_progress":
353
- return uiTheme.fg("accent", `${prefix}${checkbox.unchecked} ${label}`);
383
+ return uiTheme.fg("accent", `${prefix}${checkbox.unchecked} ${text}`);
354
384
  case "abandoned":
355
- return uiTheme.fg("error", `${prefix}${checkbox.unchecked} ${strikethroughText(label)}`);
385
+ return uiTheme.fg("error", `${prefix}${checkbox.unchecked} ${strikethroughText(text)}`);
356
386
  case "blocked": {
357
387
  const note = item.blocker ? `blocked: ${forDisplay(item.blocker)}` : "blocked";
358
- return uiTheme.fg("warning", `${prefix}${checkbox.unchecked} ${label} (${note})`);
388
+ return uiTheme.fg("warning", `${prefix}${checkbox.unchecked} ${text} (${note})`);
359
389
  }
360
390
  default:
361
391
  // A pending todo lit by a live subagent match renders accent, matching
362
392
  // the sticky HUD's convention (#5873).
363
- return uiTheme.fg(matched ? "accent" : "dim", `${prefix}${checkbox.unchecked} ${label}`);
393
+ return uiTheme.fg(matched ? "accent" : "dim", `${prefix}${checkbox.unchecked} ${text}`);
364
394
  }
365
395
  }
366
396
 
@@ -499,6 +529,7 @@ function describeTodoResult(
499
529
  tool: { title: "Todo", target: `${closed}/${total}`, targetKind: "text" },
500
530
  body: [node("checklist", { mode: "full", phases: todoChecklistPhases(phases, isMatched) }, [], "checklist")],
501
531
  preview: "none",
532
+ open: true,
502
533
  };
503
534
  }
504
535
 
@@ -592,6 +623,40 @@ export const todoToolRenderer = {
592
623
  return new Text(`${header}\n ${uiTheme.fg("dim", fallback)}`, 0, 0);
593
624
  }
594
625
 
626
+ // Everything below is fixed for this result; only the spinner frame and
627
+ // the live subagent description set vary per frame, so derive labels,
628
+ // touched phases and normalized match state once and reuse them.
629
+ const labels = new Map<TodoItem, TodoLabel>();
630
+ const labelFor = (task: TodoItem): TodoLabel => {
631
+ let label = labels.get(task);
632
+ if (!label) {
633
+ // Sanitize only for display: a mirrored Cursor snapshot carries
634
+ // provider text verbatim (ANSI/C0 would rewrite the terminal).
635
+ label = { text: forDisplay(task.content) };
636
+ labels.set(task, label);
637
+ }
638
+ return label;
639
+ };
640
+ let touchedComputed = false;
641
+ let touchedPhases: Set<string> | null = null;
642
+ let lastDescs: readonly string[] | undefined;
643
+ let candidates: string[] = [];
644
+ const matchByContent = new Map<string, boolean>();
645
+ const syncActiveDescs = (descs: readonly string[]): void => {
646
+ if (lastDescs === descs) return;
647
+ if (lastDescs && lastDescs.length === descs.length && lastDescs.every((d, i) => d === descs[i])) {
648
+ lastDescs = descs;
649
+ return;
650
+ }
651
+ lastDescs = descs;
652
+ candidates = [];
653
+ for (const desc of descs) {
654
+ const candidate = normalizeForTodoMatch(desc);
655
+ if (candidate) candidates.push(candidate);
656
+ }
657
+ matchByContent.clear();
658
+ };
659
+
595
660
  return framedToolCard(uiTheme, () => {
596
661
  const { expanded, spinnerFrame } = options;
597
662
  const multiPhase = phases.length > 1;
@@ -599,13 +664,28 @@ export const todoToolRenderer = {
599
664
  // Collapse phases this update didn't touch down to a one-line summary so
600
665
  // a single task flip doesn't redraw every phase's full task list. The
601
666
  // manual expand toggle (and the no-signal fallback) still shows all.
602
- const touched = expanded || !multiPhase ? null : computeTouchedPhases(args, phases, completedTasks);
667
+ let touched: Set<string> | null = null;
668
+ if (!expanded && multiPhase) {
669
+ if (!touchedComputed) {
670
+ touchedPhases = computeTouchedPhases(args, phases, completedTasks);
671
+ touchedComputed = true;
672
+ }
673
+ touched = touchedPhases;
674
+ }
603
675
  // A pending todo counts as active work when an in-flight subagent is
604
676
  // executing it — the transient result surfaces the same active set the
605
677
  // sticky HUD does (#5873). Empty outside an interactive session.
606
678
  const activeDescs = expanded ? [] : activeTodoDescriptionsProvider();
607
- const isMatched = (task: TodoItem): boolean =>
608
- activeDescs.length > 0 && todoMatchesAnyDescription(task.content, activeDescs);
679
+ if (activeDescs.length > 0) syncActiveDescs(activeDescs);
680
+ const isMatched = (task: TodoItem): boolean => {
681
+ if (activeDescs.length === 0) return false;
682
+ let matched = matchByContent.get(task.content);
683
+ if (matched === undefined) {
684
+ matched = matchesNormalizedCandidates(normalizeForTodoMatch(task.content), candidates);
685
+ matchByContent.set(task.content, matched);
686
+ }
687
+ return matched;
688
+ };
609
689
  const bodyLines: string[] = [];
610
690
  for (let p = 0; p < phases.length; p++) {
611
691
  const phase = phases[p];
@@ -631,7 +711,8 @@ export const todoToolRenderer = {
631
711
  items: phase.tasks,
632
712
  expanded,
633
713
  itemType: "todo",
634
- renderItem: todo => formatTodoLine(todo, uiTheme, "", completionKeys, spinnerFrame),
714
+ renderItem: todo =>
715
+ formatTodoLine(todo, labelFor(todo), uiTheme, "", completionKeys, spinnerFrame),
635
716
  },
636
717
  uiTheme,
637
718
  )
@@ -643,7 +724,15 @@ export const todoToolRenderer = {
643
724
  itemType: "todo",
644
725
  trailingSummary: selection.summary,
645
726
  renderItem: todo =>
646
- formatTodoLine(todo, uiTheme, "", completionKeys, spinnerFrame, isMatched(todo)),
727
+ formatTodoLine(
728
+ todo,
729
+ labelFor(todo),
730
+ uiTheme,
731
+ "",
732
+ completionKeys,
733
+ spinnerFrame,
734
+ isMatched(todo),
735
+ ),
647
736
  },
648
737
  uiTheme,
649
738
  );
package/src/tools/wait.ts CHANGED
@@ -275,6 +275,39 @@ function jobsRenderResult(
275
275
  });
276
276
 
277
277
  let cached: RenderCache | undefined;
278
+ // Per-job preview rows are invariant across spinner/shimmer frames (the job
279
+ // snapshots are frozen for this result), so the envelope strip, flatten and
280
+ // wrap run once per (job, expanded, width) instead of every animated frame.
281
+ const previewRows = new Map<JobSnapshot, { expanded: boolean; width: number; lines: readonly string[] }>();
282
+ const jobPreviewRows = (job: JobSnapshot, expanded: boolean, continuationWidth: number): readonly string[] => {
283
+ const hit = previewRows.get(job);
284
+ if (hit !== undefined && hit.expanded === expanded && hit.width === continuationWidth) return hit.lines;
285
+ const artifactError = job.meta?.artifactError ?? job.artifactError;
286
+ // Legacy rows did not retain full metadata. Strip the known warning
287
+ // footer so the dedicated row/root warning remains the only copy.
288
+ const previewError =
289
+ artifactError ?? (outputMeta?.source?.type !== "report" ? outputMeta?.artifactError : undefined);
290
+ const previewMeta = job.meta ?? (previewError ? { artifactError: previewError } : undefined);
291
+ const preview = flattenStructuredPreview(
292
+ stripTaskResultEnvelope(
293
+ stripOutputNotice(job.errorText?.trim() || job.resultText?.trim() || "", previewMeta).trim(),
294
+ ),
295
+ );
296
+ const lines: string[] = [];
297
+ if (preview) {
298
+ const maxLines = expanded ? PREVIEW_LINES_EXPANDED : PREVIEW_LINES_COLLAPSED;
299
+ const previewLines = getPreviewLines(
300
+ preview,
301
+ maxLines,
302
+ Math.min(PREVIEW_LINE_WIDTH, continuationWidth),
303
+ Ellipsis.Unicode,
304
+ );
305
+ const tone = job.errorText ? "error" : "dim";
306
+ for (const pl of previewLines) lines.push(` ${uiTheme.fg(tone, pl)}`);
307
+ }
308
+ previewRows.set(job, { expanded, width: continuationWidth, lines });
309
+ return lines;
310
+ };
278
311
  return {
279
312
  render(width: number): readonly string[] {
280
313
  const expanded = options.expanded;
@@ -377,30 +410,7 @@ function jobsRenderResult(
377
410
  );
378
411
  }
379
412
 
380
- // Legacy rows did not retain full metadata. Strip the known warning
381
- // footer so the dedicated row/root warning remains the only copy.
382
- const previewError =
383
- artifactError ?? (outputMeta?.source?.type !== "report" ? outputMeta?.artifactError : undefined);
384
- const previewMeta = job.meta ?? (previewError ? { artifactError: previewError } : undefined);
385
-
386
- const preview = flattenStructuredPreview(
387
- stripTaskResultEnvelope(
388
- stripOutputNotice(job.errorText?.trim() || job.resultText?.trim() || "", previewMeta).trim(),
389
- ),
390
- );
391
- if (preview) {
392
- const maxLines = expanded ? PREVIEW_LINES_EXPANDED : PREVIEW_LINES_COLLAPSED;
393
- const previewLines = getPreviewLines(
394
- preview,
395
- maxLines,
396
- Math.min(PREVIEW_LINE_WIDTH, continuationWidth),
397
- Ellipsis.Unicode,
398
- );
399
- const tone = job.errorText ? "error" : "dim";
400
- for (const pl of previewLines) {
401
- lines.push(` ${uiTheme.fg(tone, pl)}`);
402
- }
403
- }
413
+ for (const previewLine of jobPreviewRows(job, expanded, continuationWidth)) lines.push(previewLine);
404
414
  return lines;
405
415
  },
406
416
  },
@@ -171,7 +171,7 @@ export function renderSearchResult(
171
171
  if (renderedAnswer.length === 0) {
172
172
  answerLines = [theme.fg("muted", tuiText("wsNoAnswerText", "No answer text returned"))];
173
173
  } else if (args?.maxAnswerLines !== undefined && !expanded) {
174
- // CLI compact mode (`omp q`) caps the answer; the TUI passes no cap and shows it in full.
174
+ // CLI compact mode (`zetacode q`) caps the answer; the TUI passes no cap and shows it in full.
175
175
  // `renderedAnswer` is the Markdown component's shared cache — slice copies before appending.
176
176
  const capped = renderedAnswer.slice(0, args.maxAnswerLines);
177
177
  const remaining = renderedAnswer.length - capped.length;
@@ -6,7 +6,7 @@ import { type ProcWriteDetails } from "./proc-render";
6
6
  import { type CfgWriteDetails } from "./cfg-render";
7
7
  import type { FileDiagnosticsResult } from "./lsp";
8
8
  import type { OutputMeta } from "./output-meta";
9
- import type { NativeToolView, RenderResultOptions, ToolActivityContext, ToolActivitySummary } from "./renderer";
9
+ import type { NativeToolView, RenderResultOptions, ToolActivityContext, ToolActivitySummary, ToolFigure } from "./renderer";
10
10
  import { type XdevRenderDispatch, type XdevMountedRenderer } from "./xdev";
11
11
  /** Details returned by the write tool for transcript rendering. */
12
12
  export interface WriteToolDetails {
@@ -37,6 +37,12 @@ interface WriteResult {
37
37
  details?: WriteToolDetails;
38
38
  isError?: boolean;
39
39
  }
40
+ /**
41
+ * The fence a file write draws as while its content streams and after: its
42
+ * extension names a figure language and it has content, closed once the args
43
+ * are final. None for URL-card writes, or after an error.
44
+ */
45
+ declare function writeFigure(args: WriteRenderArgs | undefined, result: WriteResult | undefined, options: RenderResultOptions): ToolFigure | undefined;
40
46
  declare const writeStreamingPreviewStateKey: unique symbol;
41
47
  /**
42
48
  * Per-component state for incrementally rendering a streamed write.
@@ -75,7 +81,7 @@ export declare const writeToolRenderer: {
75
81
  renderCall(args: WriteRenderArgs, options: RenderResultOptions & WriteStreamingPreviewStateCarrier & {
76
82
  renderContext?: WriteRenderContext;
77
83
  }, uiTheme: Theme): Component | undefined;
78
- renderResult(result: WriteResult, options: RenderResultOptions & {
84
+ renderResult(result: WriteResult, options: RenderResultOptions & WriteStreamingPreviewStateCarrier & {
79
85
  renderContext?: WriteRenderContext;
80
86
  }, uiTheme: Theme, args?: WriteRenderArgs): Component;
81
87
  describeCall(args: WriteRenderArgs, options: RenderResultOptions & {
@@ -84,6 +90,7 @@ export declare const writeToolRenderer: {
84
90
  describeResult(result: WriteResult, options: RenderResultOptions & {
85
91
  renderContext?: WriteRenderContext;
86
92
  }, args?: WriteRenderArgs): NativeToolView | undefined;
93
+ figure: typeof writeFigure;
87
94
  mergeCallAndResult: true;
88
95
  forceFirstResultViewportRepaint: (args: unknown, options: RenderResultOptions) => boolean;
89
96
  };