@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
@@ -56,9 +56,20 @@ interface WordQuery extends WordCompletionQuery {
56
56
 
57
57
  /** Editor text before the word starting at `start`, truncated to its last {@link BEFORE_LIMIT} code units. */
58
58
  function textBefore(lines: readonly string[], cursorLine: number, start: number): string {
59
- let text = (lines[cursorLine] ?? "").slice(0, start);
60
- for (let line = cursorLine - 1; line >= 0 && text.length < BEFORE_LIMIT; line--) {
61
- text = `${lines[line] ?? ""}\n${text}`;
59
+ const head = (lines[cursorLine] ?? "").slice(0, start);
60
+ // Find the first line the capped tail reaches, then join once.
61
+ let length = head.length;
62
+ let first = cursorLine;
63
+ while (first > 0 && length < BEFORE_LIMIT) {
64
+ first--;
65
+ length += (lines[first] ?? "").length + 1;
66
+ }
67
+ let text = head;
68
+ if (first < cursorLine) {
69
+ const parts: string[] = [];
70
+ for (let line = first; line < cursorLine; line++) parts.push(lines[line] ?? "");
71
+ parts.push(head);
72
+ text = parts.join("\n");
62
73
  }
63
74
  if (text.length <= BEFORE_LIMIT) return text;
64
75
  let cut = text.length - BEFORE_LIMIT;
@@ -111,6 +122,16 @@ export class WordCompletionProvider implements EditorTextAssistProvider {
111
122
  #lastShown: { before: string; word: string } | undefined;
112
123
  /** What the last `getWordCompletion` returned, so a fetch repaints only when it changes that. */
113
124
  #displayed: { key: string; suffix: string | null } | undefined;
125
+ /** Last {@link #query} input snapshot (lines are copied: the editor mutates its array in place). */
126
+ #queryMemo:
127
+ | {
128
+ method: WordCompletionMethod;
129
+ lines: readonly string[];
130
+ cursorLine: number;
131
+ cursorCol: number;
132
+ query: WordQuery | undefined;
133
+ }
134
+ | undefined;
114
135
 
115
136
  /** Invoked when an asynchronous answer changes the rendered ghost text. */
116
137
  onUpdate: (() => void) | undefined;
@@ -171,11 +192,23 @@ export class WordCompletionProvider implements EditorTextAssistProvider {
171
192
 
172
193
  #query(lines: readonly string[], cursorLine: number, cursorCol: number): WordQuery | undefined {
173
194
  if (this.#method === "off") return undefined;
195
+ // Rendering asks every frame; reuse the answer until the buffer, caret, or engine changes.
196
+ const memo = this.#queryMemo;
197
+ if (
198
+ memo?.method === this.#method &&
199
+ memo.cursorLine === cursorLine &&
200
+ memo.cursorCol === cursorCol &&
201
+ memo.lines.length === lines.length &&
202
+ memo.lines.every((line, index) => line === lines[index])
203
+ ) {
204
+ return memo.query;
205
+ }
174
206
  const query = wordCompletionQuery(lines, cursorLine, cursorCol, this.#prose);
175
- if (!query) return undefined;
176
207
  // The method keeps a request still in flight for the previous engine from
177
208
  // shadowing the same word state on the new one.
178
- return { key: `${this.#method}\u0000${query.prefix}\u0000${query.before}`, ...query };
209
+ const result = query && { key: `${this.#method}\u0000${query.prefix}\u0000${query.before}`, ...query };
210
+ this.#queryMemo = { method: this.#method, lines: lines.slice(), cursorLine, cursorCol, query: result };
211
+ return result;
179
212
  }
180
213
 
181
214
  #project(query: WordQuery): string | null {
@@ -0,0 +1,2 @@
1
+ /** Splice overlay content into a base line at a specific column. Single-pass optimized. */
2
+ export declare function compositeLineAt(baseLine: string, overlayLine: string, startCol: number, overlayWidth: number, totalWidth: number): string;
@@ -0,0 +1,64 @@
1
+ import { TERMINAL } from "../terminal-capabilities";
2
+ import { extractSegments, sliceByColumn, sliceWithWidth, visibleWidth } from "../utils";
3
+
4
+ const SEGMENT_RESET = "\x1b[0m";
5
+
6
+ /** Splice overlay content into a base line at a specific column. Single-pass optimized. */
7
+ export function compositeLineAt(
8
+ baseLine: string,
9
+ overlayLine: string,
10
+ startCol: number,
11
+ overlayWidth: number,
12
+ totalWidth: number,
13
+ ): string {
14
+ // A full-width image row owns its terminal row; the writer must see its exact placement shape.
15
+ if (startCol === 0 && overlayWidth >= totalWidth && TERMINAL.isImageLine(overlayLine)) return overlayLine;
16
+ if (TERMINAL.isImageLine(baseLine)) {
17
+ // Full-width overlays such as /switch are opaque: replace the
18
+ // Unicode placeholder cells so the image cannot cover the modal.
19
+ // Partial overlays cannot safely splice placement control sequences.
20
+ if (startCol !== 0 || overlayWidth < totalWidth) return baseLine;
21
+ const overlay = sliceWithWidth(overlayLine, 0, totalWidth, true);
22
+ return SEGMENT_RESET + overlay.text + " ".repeat(Math.max(0, totalWidth - overlay.width));
23
+ }
24
+
25
+ // Single pass through baseLine extracts both before and after segments
26
+ const afterStart = startCol + overlayWidth;
27
+ const base = extractSegments(baseLine, startCol, afterStart, totalWidth - afterStart, true);
28
+
29
+ // Extract overlay with width tracking (strict=true to exclude wide chars at boundary)
30
+ const overlay = sliceWithWidth(overlayLine, 0, overlayWidth, true);
31
+
32
+ // Pad segments to target widths
33
+ const beforePad = Math.max(0, startCol - base.beforeWidth);
34
+ const overlayPad = Math.max(0, overlayWidth - overlay.width);
35
+ const actualBeforeWidth = Math.max(startCol, base.beforeWidth);
36
+ const actualOverlayWidth = Math.max(overlayWidth, overlay.width);
37
+ const afterTarget = Math.max(0, totalWidth - actualBeforeWidth - actualOverlayWidth);
38
+ const afterPad = Math.max(0, afterTarget - base.afterWidth);
39
+
40
+ // Compose result
41
+ const r = SEGMENT_RESET;
42
+ const result =
43
+ base.before +
44
+ " ".repeat(beforePad) +
45
+ r +
46
+ overlay.text +
47
+ " ".repeat(overlayPad) +
48
+ r +
49
+ base.after +
50
+ " ".repeat(afterPad);
51
+
52
+ // CRITICAL: Always verify and truncate to terminal width.
53
+ // This is the final safeguard against width overflow which would crash the TUI.
54
+ // Width tracking can drift from actual visible width due to:
55
+ // - Complex ANSI/OSC sequences (hyperlinks, colors)
56
+ // - Wide characters at segment boundaries
57
+ // - Edge cases in segment extraction
58
+ const resultWidth = visibleWidth(result);
59
+ if (resultWidth <= totalWidth) {
60
+ return result;
61
+ }
62
+ // Truncate with strict=true to ensure we don't exceed totalWidth
63
+ return sliceByColumn(result, 0, totalWidth, true);
64
+ }
@@ -92,6 +92,7 @@ export declare class OutputPane implements Component {
92
92
  describe(_cx: DescribeContext): NativeNode;
93
93
  render(width: number): readonly string[];
94
94
  invalidate(): void;
95
+ releaseRenderCaches(): void;
95
96
  }
96
97
  /** Default styling for plain tool output while preserving existing ANSI. */
97
98
  export declare function styleToolOutputLine(line: string, theme: Theme): string;
@@ -302,6 +302,12 @@ export class OutputPane implements Component {
302
302
  this.#text.invalidate();
303
303
  }
304
304
 
305
+ releaseRenderCaches(): void {
306
+ this.#renderKey = "";
307
+ this.#text.setText("");
308
+ this.#text.releaseRenderCaches();
309
+ }
310
+
305
311
  #appendToTail(text: string): void {
306
312
  if (!text) return;
307
313
  if (this.#lines.length === 0) this.#lines.push("");
@@ -298,6 +298,7 @@ export interface RenderedStringCache {
298
298
  export declare function createRenderedStringCache(): RenderedStringCache;
299
299
  /** Drop the memo so the next lookup re-renders (e.g. the render function identity changed). */
300
300
  export declare function invalidateRenderedStringCache(cache: RenderedStringCache): void;
301
+ export declare function releaseRenderedStringCache(cache: RenderedStringCache): void;
301
302
  /** Reuse a rendered string while its theme, expansion, content, and salt match. */
302
303
  export declare function cachedRenderedString(cache: RenderedStringCache | undefined, theme: Theme, expanded: boolean, salt: string, content: string, render: () => string): string;
303
304
  /**
@@ -1128,6 +1128,9 @@ export function createCachedComponent(
1128
1128
  invalidate() {
1129
1129
  cached = undefined;
1130
1130
  },
1131
+ releaseRenderCaches() {
1132
+ cached = undefined;
1133
+ },
1131
1134
  };
1132
1135
  }
1133
1136
 
@@ -1160,6 +1163,14 @@ export function invalidateRenderedStringCache(cache: RenderedStringCache): void
1160
1163
  cache.theme = null;
1161
1164
  }
1162
1165
 
1166
+ export function releaseRenderedStringCache(cache: RenderedStringCache): void {
1167
+ cache.theme = null;
1168
+ cache.expanded = false;
1169
+ cache.salt = "";
1170
+ cache.content = "";
1171
+ cache.value = "";
1172
+ }
1173
+
1163
1174
  /** Reuse a rendered string while its theme, expansion, content, and salt match. */
1164
1175
  export function cachedRenderedString(
1165
1176
  cache: RenderedStringCache | undefined,
@@ -46,6 +46,11 @@ export interface ToolCardOptions {
46
46
  paddingY?: number;
47
47
  ignoreTight?: boolean;
48
48
  onInvalidate?: () => void;
49
+ /**
50
+ * Drop builder-owned memos the next build recomputes. Called by
51
+ * {@link ToolCard.releaseRenderCaches}, which never calls `onInvalidate`.
52
+ */
53
+ onReleaseRenderCaches?: () => void;
49
54
  onDispose?: () => void;
50
55
  }
51
56
  /** Map transcript lifecycle terminology onto the output frame's visual states. */
@@ -71,6 +76,7 @@ export declare class ToolCard implements Component {
71
76
  handleInput(data: string): void;
72
77
  setIgnoreTight(ignore: boolean): void;
73
78
  invalidate(): void;
79
+ releaseRenderCaches(): void;
74
80
  dispose(): void;
75
81
  }
76
82
  /** Construct a lazy framed tool card. */
@@ -69,6 +69,11 @@ export interface ToolCardOptions {
69
69
  paddingY?: number;
70
70
  ignoreTight?: boolean;
71
71
  onInvalidate?: () => void;
72
+ /**
73
+ * Drop builder-owned memos the next build recomputes. Called by
74
+ * {@link ToolCard.releaseRenderCaches}, which never calls `onInvalidate`.
75
+ */
76
+ onReleaseRenderCaches?: () => void;
72
77
  onDispose?: () => void;
73
78
  }
74
79
 
@@ -381,6 +386,16 @@ export class ToolCard implements Component {
381
386
  this.#options.onInvalidate?.();
382
387
  }
383
388
 
389
+ releaseRenderCaches(): void {
390
+ this.#block.invalidate();
391
+ this.#lastBlockOptions = undefined;
392
+ this.#plainKey = "";
393
+ this.#plainText.setText("");
394
+ this.#plainText.releaseRenderCaches();
395
+ for (const child of this.#renderedChildren) child.releaseRenderCaches?.();
396
+ this.#options.onReleaseRenderCaches?.();
397
+ }
398
+
384
399
  dispose(): void {
385
400
  if (this.#disposed) return;
386
401
  this.#disposed = true;
@@ -20,6 +20,8 @@ export declare class WidthAwareText implements Component {
20
20
  setCustomBgFn(customBgFn?: (text: string) => string): void;
21
21
  setIgnoreTight(ignore: boolean): this;
22
22
  invalidate(): void;
23
+ /** The formatted text stays: the inner `Text` renders from it, so dropping it would free nothing. */
24
+ releaseRenderCaches(): void;
23
25
  /**
24
26
  * Re-run the formatter on the next render because its inputs changed.
25
27
  * Unlike {@link invalidate}, the inner `Text` keeps its wrap cache, so a
@@ -51,6 +51,11 @@ export class WidthAwareText implements Component {
51
51
  this.#inner.invalidate();
52
52
  }
53
53
 
54
+ /** The formatted text stays: the inner `Text` renders from it, so dropping it would free nothing. */
55
+ releaseRenderCaches(): void {
56
+ this.#inner.releaseRenderCaches();
57
+ }
58
+
54
59
  /**
55
60
  * Re-run the formatter on the next render because its inputs changed.
56
61
  * Unlike {@link invalidate}, the inner `Text` keeps its wrap cache, so a
@@ -1,4 +1,5 @@
1
1
  import type { Model } from "@linxiraos/pi-ai";
2
+ import { modelKind } from "@linxiraos/pi-catalog/types";
2
3
  import type { SgrMouseEvent } from "../../mouse";
3
4
  import { Text } from "../../components/text";
4
5
  import { WizardStep } from "../../components/wizard-step";
@@ -114,7 +115,7 @@ class ModelSceneController implements SetupSceneController {
114
115
  const { available, all, current } = this.#host.ctx.getModels();
115
116
  const source = this.#host.ctx.modelSource;
116
117
  const roles = resolveRoleAssignments(source, all, available);
117
- const items = buildBrowserItems(available);
118
+ const items = buildBrowserItems(available.filter(model => modelKind(model) === "chat"));
118
119
  sortModelItems(items, { roles, mruOrder: source.mruOrder });
119
120
  this.#browser.setRoles(roles);
120
121
  this.#browser.setMruOrder(source.mruOrder);
@@ -27,18 +27,11 @@ const RESET = "\x1b[0m";
27
27
  const MIN_SCENE_WIDTH = 56;
28
28
  const MIN_SCENE_HEIGHT = 22;
29
29
 
30
+ /** Skip affordance; built at render time so it follows the live symbol preset. */
30
31
  function skipHint(): string {
31
- return `press ${formatKeyHint("enter")} to skip`;
32
+ return tuiText("setupSkipHint", "press {key} to skip").replace("{key}", formatKeyHint("enter"));
32
33
  }
33
34
 
34
- /** Density ramp for the rippling water, lightest → heaviest. */
35
- const WATER_RAMP = [
36
- { min: 0.62, char: "█" },
37
- { min: 0.5, char: "▓" },
38
- { min: 0.36, char: "▒" },
39
- { min: 0.24, char: "░" },
40
- ];
41
-
42
35
  function starAt(x: number, y: number, frame: number): string {
43
36
  const hash = (x * 73856093) ^ (y * 19349663) ^ (frame * 83492791);
44
37
  const bucket = Math.abs(hash) % 97;
@@ -85,35 +78,88 @@ function waterJitter(x: number, y: number): number {
85
78
  }
86
79
 
87
80
  /**
88
- * Rippling water amplitude in [0,1] at (x, y): three travelling sine waves
89
- * interfere, then a radial edge falloff and a downward fade concentrate the
90
- * ripples beneath the mark and dissolve them toward the edges/bottom. `t`
91
- * advances each tick, so the surface drifts.
81
+ * Time-invariant terms of the rippling water for one screen size: three
82
+ * travelling sine waves interfere, then a radial edge falloff and a downward
83
+ * fade concentrate the ripples beneath the mark and dissolve them toward the
84
+ * edges/bottom. Only the sine phases move per tick, so everything else is
85
+ * computed once per size.
92
86
  */
93
- function waterAmplitude(
94
- x: number,
95
- y: number,
96
- cx: number,
97
- waterTop: number,
98
- waterHeight: number,
99
- width: number,
100
- t: number,
101
- ): number {
102
- const dx = (x - cx) / 2;
103
- const dy = y - waterTop;
104
- const dist = Math.sqrt(dx * dx + dy * dy);
105
- const wave =
106
- 0.5 * Math.sin(dist * 0.55 - t) +
107
- 0.3 * Math.sin(x * 0.22 + y * 0.45 - t * 0.7) +
108
- 0.2 * Math.sin(Math.abs(dx) * 0.8 + dy * 0.5 - t * 1.4);
109
- const level = 0.5 + 0.5 * wave;
110
- const edge = Math.max(0, 1 - Math.abs(x - cx) / (width * 0.5));
111
- const fade = Math.max(0, 1 - (dy / Math.max(1, waterHeight)) * 0.55);
112
- return level * edge ** 0.7 * fade;
87
+ interface WaterField {
88
+ readonly width: number;
89
+ readonly height: number;
90
+ readonly waterTop: number;
91
+ /** Per cell (row-major from `waterTop`): `dist * 0.55`. */
92
+ readonly radial: Float64Array;
93
+ /** Per cell: `x * 0.22 + y * 0.45`. */
94
+ readonly diagonal: Float64Array;
95
+ /** Per cell: `|dx| * 0.8 + dy * 0.5`. */
96
+ readonly cross: Float64Array;
97
+ /** Per cell: `(waterJitter(x, y) - 0.5) * 0.06`. */
98
+ readonly jitter: Float64Array;
99
+ /** Per column: `edge ** 0.7`. */
100
+ readonly edge: Float64Array;
101
+ /** Per water row: downward fade. */
102
+ readonly fade: Float64Array;
103
+ }
104
+
105
+ let waterField: WaterField | undefined;
106
+
107
+ function getWaterField(width: number, height: number, cx: number, waterTop: number): WaterField {
108
+ const cached = waterField;
109
+ if (cached && cached.width === width && cached.height === height && cached.waterTop === waterTop) return cached;
110
+ const waterHeight = Math.max(1, height - waterTop);
111
+ const rows = Math.max(0, height - waterTop);
112
+ const radial = new Float64Array(rows * width);
113
+ const diagonal = new Float64Array(rows * width);
114
+ const cross = new Float64Array(rows * width);
115
+ const jitter = new Float64Array(rows * width);
116
+ const edge = new Float64Array(width);
117
+ const fade = new Float64Array(rows);
118
+ for (let x = 0; x < width; x++) edge[x] = Math.max(0, 1 - Math.abs(x - cx) / (width * 0.5)) ** 0.7;
119
+ for (let row = 0; row < rows; row++) {
120
+ const y = waterTop + row;
121
+ const dy = y - waterTop;
122
+ fade[row] = Math.max(0, 1 - (dy / waterHeight) * 0.55);
123
+ for (let x = 0; x < width; x++) {
124
+ const i = row * width + x;
125
+ const dx = (x - cx) / 2;
126
+ radial[i] = Math.sqrt(dx * dx + dy * dy) * 0.55;
127
+ diagonal[i] = x * 0.22 + y * 0.45;
128
+ cross[i] = Math.abs(dx) * 0.8 + dy * 0.5;
129
+ jitter[i] = (waterJitter(x, y) - 0.5) * 0.06;
130
+ }
131
+ }
132
+ waterField = { width, height, waterTop, radial, diagonal, cross, jitter, edge, fade };
133
+ return waterField;
134
+ }
135
+
136
+ /** Water glyph for an amplitude, lightest → heaviest density ramp; undefined below the floor. */
137
+ function waterChar(amp: number): string | undefined {
138
+ if (amp > 0.62) return "█";
139
+ if (amp > 0.5) return "▓";
140
+ if (amp > 0.36) return "▒";
141
+ if (amp > 0.24) return "░";
142
+ return undefined;
113
143
  }
114
144
 
145
+ /** Reused cell grid; resized when the screen changes. */
146
+ let splashCells: string[][] = [];
147
+
148
+ function resetCells(width: number, height: number): string[][] {
149
+ if (splashCells.length !== height || splashCells[0]?.length !== width) {
150
+ // oxlint-disable-next-line unicorn/no-new-array -- length preallocation
151
+ splashCells = Array.from({ length: height }, () => new Array<string>(width).fill(" "));
152
+ } else {
153
+ for (const row of splashCells) row.fill(" ");
154
+ }
155
+ return splashCells;
156
+ }
157
+
158
+ /** Per-frame gradient escapes, one per screen diagonal (the gradient only varies along it). */
159
+ let diagonalEscapes: (string | undefined)[] = [];
160
+
115
161
  /**
116
- * Animated setup splash, in the spirit of the omp landing page: the brand ζ
162
+ * Animated setup splash, in the spirit of the Zeta visual identity: the brand ζ
117
163
  * mark rendered with the live diagonal gradient + shine sweep, rising out of a
118
164
  * rippling, gradient-lit water surface, under a faint twinkling starfield. The
119
165
  * mark and water share one continuous gradient so the sweep reads across the
@@ -132,23 +178,46 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
132
178
  const cx = Math.floor(w / 2);
133
179
  const surfaceTime = frame * 0.13;
134
180
 
135
- // [suppressed] length preallocation
136
- const cells: string[][] = Array.from({ length: h }, () => new Array<string>(w).fill(" "));
181
+ const cells = resetCells(w, h);
137
182
  const put = (x: number, y: number, glyph: string): void => {
138
183
  if (y >= 0 && y < h && x >= 0 && x < w) cells[y][x] = glyph;
139
184
  };
185
+ // Every cell on a diagonal shares one gradient position, so resolve each
186
+ // escape once per frame instead of once per cell.
187
+ const diagonals = w + h - 1;
188
+ if (diagonalEscapes.length !== diagonals) diagonalEscapes = Array.from({ length: diagonals });
189
+ else diagonalEscapes.fill(undefined);
190
+ const escapes = diagonalEscapes;
191
+ const gradient = (x: number, y: number): string => {
192
+ const d = x + (h - 1 - y);
193
+ let escape = escapes[d];
194
+ if (escape === undefined) {
195
+ escape = gradientEscape(screenGradientT(x, y, w, h, phase), shine);
196
+ escapes[d] = escape;
197
+ }
198
+ return escape;
199
+ };
140
200
 
141
201
  const hx = Math.floor((w - LOGO_WIDTH) / 2);
142
202
  const hy = Math.max(2, Math.floor(h * 0.16));
143
203
  const waterTop = hy + LOGO_HEIGHT;
144
- const waterHeight = Math.max(1, h - waterTop);
145
204
 
146
205
  // 1. rippling water surface (shares the screen-wide gradient with the mark)
206
+ const field = getWaterField(w, h, cx, waterTop);
207
+ const tDiagonal = surfaceTime * 0.7;
208
+ const tCross = surfaceTime * 1.4;
147
209
  for (let y = waterTop; y < h; y++) {
210
+ const row = y - waterTop;
211
+ const fade = field.fade[row];
148
212
  for (let x = 0; x < w; x++) {
149
- const amp = waterAmplitude(x, y, cx, waterTop, waterHeight, w, surfaceTime) + (waterJitter(x, y) - 0.5) * 0.06;
150
- const cell = WATER_RAMP.find(step => amp > step.min);
151
- if (cell) put(x, y, gradientEscape(screenGradientT(x, y, w, h, phase), shine) + cell.char + RESET);
213
+ const i = row * w + x;
214
+ const wave =
215
+ 0.5 * Math.sin(field.radial[i] - surfaceTime) +
216
+ 0.3 * Math.sin(field.diagonal[i] - tDiagonal) +
217
+ 0.2 * Math.sin(field.cross[i] - tCross);
218
+ const level = 0.5 + 0.5 * wave;
219
+ const char = waterChar(level * field.edge[x] * fade + field.jitter[i]);
220
+ if (char) cells[y][x] = gradient(x, y) + char + RESET;
152
221
  }
153
222
  }
154
223
  // 2. twinkling starfield in the sky above the water
@@ -162,18 +231,15 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
162
231
  LARGE_LOGO.forEach((line, row) => {
163
232
  let col = 0;
164
233
  for (const ch of line) {
165
- if (ch !== " ") {
166
- put(
167
- hx + col,
168
- hy + row,
169
- gradientEscape(screenGradientT(hx + col, hy + row, w, h, phase), shine) + ch + RESET,
170
- );
171
- }
234
+ const x = hx + col;
235
+ const y = hy + row;
236
+ if (ch !== " " && y >= 0 && y < h && x >= 0 && x < w) cells[y][x] = gradient(x, y) + ch + RESET;
172
237
  col++;
173
238
  }
174
239
  });
175
- // 4. skip hint on a cleared strip at the bottom so it stays legible over the water
176
- const hint = tuiText("setupSkipHint", "press enter to skip");
240
+ // 4. skip hint on a cleared strip at the bottom so it stays legible over the water.
241
+ // `formatKeyHint` yields plain glyphs (no ANSI), so per-character dim styling is safe.
242
+ const hint = skipHint();
177
243
  const hintWidth = visibleWidth(hint);
178
244
  const hintStart = Math.floor((w - hintWidth) / 2);
179
245
  const hintRow = h - 1;
@@ -219,9 +285,6 @@ function renderCompactSplash(width: number, height: number, phase: number, shine
219
285
  lines.push(width > 0 ? padToWidth(item !== undefined ? centerLine(item, width) : "", width) : "");
220
286
  }
221
287
  if (height > 2)
222
- lines[height - 2] =
223
- width > 0
224
- ? padToWidth(centerLine(theme.fg("dim", tuiText("setupSkipHint", "press enter to skip")), width), width)
225
- : "";
288
+ lines[height - 2] = width > 0 ? padToWidth(centerLine(theme.fg("dim", skipHint()), width), width) : "";
226
289
  return lines;
227
290
  }
@@ -29,6 +29,7 @@ export interface SetupHost extends SetupUiHost {
29
29
  readonly disabledProviders: readonly string[];
30
30
  readonly authStorage: AuthStorage;
31
31
  readonly modelSource: ModelBrowserSource;
32
+ /** Available and catalog models of every kind; role chips resolve over all of them, rows list chat models. */
32
33
  getModels(): { available: Model[]; all: Model[]; current: Model | undefined };
33
34
  refreshModels(): Promise<void>;
34
35
  selectModel(model: Model, selector: string): Promise<void>;