@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
@@ -92,6 +92,7 @@ export declare class ScrollView implements Component {
92
92
  /** Apply Arrow, Shift+Arrow, Page, Home, and End navigation keys. */
93
93
  handleScrollKey(data: string): boolean;
94
94
  invalidate(): void;
95
+ releaseRenderCaches(): void;
95
96
  /** Permanently release a wrapped child's timers, subscriptions, and other resources. */
96
97
  dispose(): void;
97
98
  /**
@@ -408,6 +408,11 @@ export class ScrollView implements Component {
408
408
  this.#child?.invalidate?.();
409
409
  }
410
410
 
411
+ releaseRenderCaches(): void {
412
+ this.#cache = undefined;
413
+ this.#child?.releaseRenderCaches?.();
414
+ }
415
+
411
416
  /** Permanently release a wrapped child's timers, subscriptions, and other resources. */
412
417
  dispose(): void {
413
418
  if (this.#disposed) return;
@@ -15,6 +15,7 @@ export declare class Section implements Component {
15
15
  #private;
16
16
  constructor(options: SectionOptions);
17
17
  invalidate(): void;
18
+ releaseRenderCaches(): void;
18
19
  dispose(): void;
19
20
  /** A `section` headed by the styled title; the rule is the terminal's. */
20
21
  describe(_cx: DescribeContext): NativeNode;
@@ -51,6 +51,11 @@ export class Section implements Component {
51
51
  this.#cache = undefined;
52
52
  }
53
53
 
54
+ releaseRenderCaches(): void {
55
+ this.#cache = undefined;
56
+ if (isComponent(this.#options.body)) this.#options.body.releaseRenderCaches?.();
57
+ }
58
+
54
59
  dispose(): void {
55
60
  if (this.#disposed) return;
56
61
  this.#disposed = true;
@@ -346,6 +346,8 @@ export class SettingsList implements Component {
346
346
  return { row: id, option: component.selectList.getSelectedItem()?.value };
347
347
  }
348
348
  if (component instanceof TextFormField) {
349
+ // Native inline text editing has no error slot; show the form until corrected.
350
+ if (component.hasError) return null;
349
351
  const draft = component.getValue();
350
352
  return {
351
353
  row: id,
@@ -29,6 +29,7 @@ export declare class Table implements Component {
29
29
  constructor(rows: readonly (readonly TableCell[])[], columns: readonly TableColumn[], options?: TableOptions);
30
30
  setRows(rows: readonly (readonly TableCell[])[]): boolean;
31
31
  invalidate(): void;
32
+ releaseRenderCaches(): void;
32
33
  /**
33
34
  * A native `table`. Column priorities carry over unchanged (lower gives
34
35
  * way first); truncating columns truncate at the end, the rest keep
@@ -117,6 +117,10 @@ export class Table implements Component {
117
117
  this.#native = undefined;
118
118
  }
119
119
 
120
+ releaseRenderCaches(): void {
121
+ this.#cache = undefined;
122
+ }
123
+
120
124
  /**
121
125
  * A native `table`. Column priorities carry over unchanged (lower gives
122
126
  * way first); truncating columns truncate at the end, the rest keep
@@ -26,6 +26,7 @@ export declare class Text implements Component {
26
26
  */
27
27
  setStyleFn(styleFn?: (text: string) => string): this;
28
28
  invalidate(): void;
29
+ releaseRenderCaches(): void;
29
30
  /**
30
31
  * Wrapped text; styling re-derived from the styled source so a theme swap
31
32
  * re-resolves tokens. A background fill becomes the node's tone.
@@ -109,6 +109,13 @@ export class Text implements Component {
109
109
  this.#cachedLines = undefined;
110
110
  }
111
111
 
112
+ releaseRenderCaches(): void {
113
+ this.#cachedText = undefined;
114
+ this.#cachedWidth = undefined;
115
+ this.#cachedWidthConfigEpoch = undefined;
116
+ this.#cachedLines = undefined;
117
+ }
118
+
112
119
  /**
113
120
  * Wrapped text; styling re-derived from the styled source so a theme swap
114
121
  * re-resolves tokens. A background fill becomes the node's tone.
@@ -34,6 +34,13 @@ export interface FlattenTreeOptions<T, K extends TreeKey> {
34
34
  rootDepth?: number;
35
35
  /** Override the display depth inherited by direct children. */
36
36
  getChildDepth?: (item: T, row: TreeRow<T, K>, children: readonly T[]) => number;
37
+ /**
38
+ * Reuse the parent ancestor list when a child keeps the same display depth
39
+ * and the current item has no siblings (`siblingCount === 1`). Otherwise
40
+ * retain the current item as an ancestor so connector gutters remain
41
+ * accurate, including when the item has multiple children.
42
+ */
43
+ compactSameDepthAncestors?: boolean;
37
44
  /** Stop projection after this many structural items. Omit for no traversal cap. */
38
45
  maxItems?: number;
39
46
  }
@@ -43,17 +43,41 @@ export interface FlattenTreeOptions<T, K extends TreeKey> {
43
43
  rootDepth?: number;
44
44
  /** Override the display depth inherited by direct children. */
45
45
  getChildDepth?: (item: T, row: TreeRow<T, K>, children: readonly T[]) => number;
46
+ /**
47
+ * Reuse the parent ancestor list when a child keeps the same display depth
48
+ * and the current item has no siblings (`siblingCount === 1`). Otherwise
49
+ * retain the current item as an ancestor so connector gutters remain
50
+ * accurate, including when the item has multiple children.
51
+ */
52
+ compactSameDepthAncestors?: boolean;
46
53
  /** Stop projection after this many structural items. Omit for no traversal cap. */
47
54
  maxItems?: number;
48
55
  }
49
56
 
57
+ /**
58
+ * Ancestors live in a shared parent chain instead of a per-row array: a linear
59
+ * conversation is an n-deep path, and copying the array at every level costs
60
+ * O(n²) memory — 80k-entry sessions exhausted RAM opening `/tree`. Rows build
61
+ * the array lazily, so only rendered rows pay O(depth).
62
+ */
63
+ interface AncestorChain<K extends TreeKey> {
64
+ value: TreeAncestor<K>;
65
+ parent: AncestorChain<K> | undefined;
66
+ }
67
+
68
+ function ancestorArray<K extends TreeKey>(chain: AncestorChain<K> | undefined): readonly TreeAncestor<K>[] {
69
+ const out: TreeAncestor<K>[] = [];
70
+ for (let node = chain; node !== undefined; node = node.parent) out.push(node.value);
71
+ return out.reverse();
72
+ }
73
+
50
74
  interface PendingTreeRow<T, K extends TreeKey> {
51
75
  item: T;
52
76
  parentKey: K | undefined;
53
77
  depth: number;
54
78
  siblingIndex: number;
55
79
  siblingCount: number;
56
- ancestors: readonly TreeAncestor<K>[];
80
+ chain: AncestorChain<K> | undefined;
57
81
  }
58
82
 
59
83
  interface FlattenTreeResult<T, K extends TreeKey> {
@@ -77,13 +101,14 @@ function projectTree<T, K extends TreeKey>(options: FlattenTreeOptions<T, K>): F
77
101
  depth: rootDepth,
78
102
  siblingIndex: index,
79
103
  siblingCount: roots.length,
80
- ancestors: [],
104
+ chain: undefined,
81
105
  });
82
106
  }
83
107
 
84
108
  while (stack.length > 0 && rows.length < maxItems) {
85
109
  const pending = stack.pop()!;
86
110
  const key = options.getKey(pending.item);
111
+ const chain = pending.chain;
87
112
  const row: TreeRow<T, K> = {
88
113
  item: pending.item,
89
114
  key,
@@ -93,7 +118,9 @@ function projectTree<T, K extends TreeKey>(options: FlattenTreeOptions<T, K>): F
93
118
  siblingIndex: pending.siblingIndex,
94
119
  siblingCount: pending.siblingCount,
95
120
  isLast: pending.siblingIndex === pending.siblingCount - 1,
96
- ancestors: pending.ancestors,
121
+ get ancestors() {
122
+ return ancestorArray(chain);
123
+ },
97
124
  };
98
125
  rows.push(row);
99
126
 
@@ -104,10 +131,13 @@ function projectTree<T, K extends TreeKey>(options: FlattenTreeOptions<T, K>): F
104
131
  0,
105
132
  Math.trunc(options.getChildDepth?.(pending.item, row, children) ?? pending.depth + 1),
106
133
  );
107
- const ancestors: readonly TreeAncestor<K>[] = [
108
- ...pending.ancestors,
109
- { key, depth: pending.depth, isLast: row.isLast, siblingCount: pending.siblingCount },
110
- ];
134
+ const childChain: AncestorChain<K> | undefined =
135
+ options.compactSameDepthAncestors && childDepth === pending.depth && pending.siblingCount === 1
136
+ ? chain
137
+ : {
138
+ value: { key, depth: pending.depth, isLast: row.isLast, siblingCount: pending.siblingCount },
139
+ parent: chain,
140
+ };
111
141
  for (let index = children.length - 1; index >= 0; index--) {
112
142
  stack.push({
113
143
  item: children[index],
@@ -115,7 +145,7 @@ function projectTree<T, K extends TreeKey>(options: FlattenTreeOptions<T, K>): F
115
145
  depth: childDepth,
116
146
  siblingIndex: index,
117
147
  siblingCount: children.length,
118
- ancestors,
148
+ chain: childChain,
119
149
  });
120
150
  }
121
151
  }
@@ -9,6 +9,7 @@ export declare class TruncatedText implements Component {
9
9
  /** Return bounded source text and the last-known truncation state. */
10
10
  debugState(): Record<string, unknown>;
11
11
  invalidate(): void;
12
+ releaseRenderCaches(): void;
12
13
  /** First line only, clamped to one visual line with an end ellipsis. */
13
14
  describe(_cx: DescribeContext): NativeNode;
14
15
  render(width: number): readonly string[];
@@ -41,6 +41,11 @@ export class TruncatedText implements Component {
41
41
  this.#cachedLines = undefined;
42
42
  }
43
43
 
44
+ releaseRenderCaches(): void {
45
+ this.#cachedWidth = -1;
46
+ this.#cachedLines = undefined;
47
+ }
48
+
44
49
  /** First line only, clamped to one visual line with an end ellipsis. */
45
50
  describe(_cx: DescribeContext): NativeNode {
46
51
  if (this.#native) return this.#native;
@@ -50,7 +50,8 @@ export function hasLinuxDesktopSession(
50
50
  if (platform !== "linux") return false;
51
51
  if (env.DBUS_SESSION_BUS_ADDRESS) return true;
52
52
  const runtimeDir = env.XDG_RUNTIME_DIR;
53
- return Boolean(runtimeDir && fileExists(path.join(runtimeDir, "bus")));
53
+ // Only reached for Linux, so join with POSIX separators regardless of the host.
54
+ return Boolean(runtimeDir && fileExists(path.posix.join(runtimeDir, "bus")));
54
55
  }
55
56
 
56
57
  /**
@@ -175,7 +176,7 @@ export function sendDesktopNotification(message: string | TerminalNotification):
175
176
  try {
176
177
  // `.unref()` lets the event loop exit while the notifier is still running.
177
178
  // Without it, an unresponsive D-Bus activation (slow `notify-send`, hung
178
- // `gdbus` waiting on a stalled session bus) would keep `omp` alive past
179
+ // `gdbus` waiting on a stalled session bus) would keep `zetacode` alive past
179
180
  // the renderer's shutdown — a completion toast must never delay process
180
181
  // exit. Ignored stdio alone does not detach the child from the parent's
181
182
  // reference count.
package/src/fuzzy.d.ts CHANGED
@@ -16,6 +16,20 @@ export interface FuzzyFilterResult<T> {
16
16
  score: number;
17
17
  }
18
18
  export declare function fuzzyMatch(query: string, text: string): FuzzyMatch;
19
+ /**
20
+ * A query normalized and tokenized once for matching against many texts.
21
+ *
22
+ * `fuzzyMatch(query, text)` re-normalizes `query` (and rebuilds its
23
+ * alphanumeric-swap variants) on every call; filters that test one query
24
+ * against every row should build one `FuzzyQuery` per keystroke instead.
25
+ * `query.match(text)` returns exactly what `fuzzyMatch(query, text)` returns.
26
+ */
27
+ export declare class FuzzyQuery {
28
+ #private;
29
+ constructor(query: string);
30
+ /** Same result as `fuzzyMatch(query, text)`; `text` may be a prepared {@link FuzzyText}. */
31
+ match(text: string | FuzzyText): FuzzyMatch;
32
+ }
19
33
  /**
20
34
  * A text prepared once for repeated fuzzy matching.
21
35
  *
@@ -30,7 +44,21 @@ export declare class FuzzyText {
30
44
  #private;
31
45
  constructor(text: string);
32
46
  /** Match `query` (space-separated tokens; all must match) against the prepared text. */
33
- match(query: string): FuzzyMatch;
47
+ match(query: string | FuzzyQuery): FuzzyMatch;
48
+ }
49
+ /**
50
+ * Items prepared once for repeated {@link fuzzyRank}-equivalent ranking.
51
+ *
52
+ * `fuzzyRank` resolves each candidate's search index through the bounded module
53
+ * cache, which holds at most {@link INDEX_CACHE_MAX} entries; larger candidate
54
+ * lists (the full model catalog) rebuild the overflow on every query. Hold one
55
+ * corpus per stable candidate list and call {@link rank} per keystroke instead.
56
+ */
57
+ export declare class FuzzyCorpus<T> {
58
+ #private;
59
+ constructor(items: readonly T[], getText: (item: T) => string);
60
+ /** Same results as `fuzzyRank(items, query, getText)` for the prepared items. */
61
+ rank(query: string): FuzzyFilterResult<T>[];
34
62
  }
35
63
  /**
36
64
  * Filter and sort items by fuzzy match quality (best matches first).
package/src/fuzzy.ts CHANGED
@@ -68,27 +68,41 @@ function normalizeForSearch(value: string): string {
68
68
  // index across keystrokes eliminates the redundant normalize + word-split + Set
69
69
  // build on every character. Consumers only read the result, so sharing is safe.
70
70
  //
71
- // Admission is conservative so the cache helps the repeated-filter hot path
72
- // without paying for one-off text: only short texts are cached (long inputs —
73
- // pasted prompts, transcripts searched via the message selector — would bloat
74
- // memory), and admission stops at the cap instead of evicting, so a stream of
75
- // unique texts (message/session search) can't churn the map.
71
+ // Only short texts are cached (long inputs — pasted prompts, transcripts
72
+ // searched via the message selector — would bloat memory). The map is a
73
+ // bounded LRU: a hit re-inserts the entry (Map iteration order is insertion
74
+ // order) and a miss at capacity evicts the oldest entry, so a burst of one-off
75
+ // texts can't pin itself forever and starve later selectors of the cache.
76
+ // A single ranking pass only admits/promotes its first INDEX_CACHE_MAX
77
+ // candidates; the tail of a larger list is looked up without promotion and
78
+ // built uncached otherwise, so re-ranking an oversized list keeps its head
79
+ // cached instead of cyclically evicting every entry. Candidate lists larger
80
+ // than the cap should hold a {@link FuzzyCorpus}.
76
81
  const INDEX_CACHE_MAX = 4096;
77
82
  const MAX_CACHED_TEXT_LEN = 4096;
78
83
  const indexCache = new Map<string, SearchIndex>();
79
84
 
80
- function buildSearchIndex(text: string): SearchIndex {
85
+ function buildSearchIndex(text: string, admit = true): SearchIndex {
81
86
  // Long inputs (pasted prompts, transcripts) are never cached; bypass the Map
82
87
  // entirely so they don't pay a hash lookup on every search.
83
88
  if (text.length > MAX_CACHED_TEXT_LEN) return buildUncachedSearchIndex(text);
84
89
 
85
90
  const cached = indexCache.get(text);
86
- if (cached !== undefined) return cached;
91
+ if (cached !== undefined) {
92
+ if (admit) {
93
+ indexCache.delete(text);
94
+ indexCache.set(text, cached);
95
+ }
96
+ return cached;
97
+ }
87
98
 
88
99
  const result = buildUncachedSearchIndex(text);
89
- if (indexCache.size < INDEX_CACHE_MAX) {
90
- indexCache.set(text, result);
100
+ if (!admit) return result;
101
+ if (indexCache.size >= INDEX_CACHE_MAX) {
102
+ const oldest = indexCache.keys().next().value;
103
+ if (oldest !== undefined) indexCache.delete(oldest);
91
104
  }
105
+ indexCache.set(text, result);
92
106
  return result;
93
107
  }
94
108
 
@@ -307,11 +321,11 @@ function scoreTokenDirect(token: string, index: SearchIndex): FuzzyMatch {
307
321
  return best ?? { matches: false, score: 0 };
308
322
  }
309
323
 
310
- function scoreToken(token: string, index: SearchIndex): FuzzyMatch {
324
+ function scoreToken(token: string, swapVariants: readonly string[], index: SearchIndex): FuzzyMatch {
311
325
  let best = scoreTokenDirect(token, index);
312
326
  if (best.matches) return best;
313
327
 
314
- for (const variant of buildAlphanumericSwapQueries(token)) {
328
+ for (const variant of swapVariants) {
315
329
  const match = scoreTokenDirect(variant, index);
316
330
  if (!match.matches) continue;
317
331
  const score = match.score + ALPHANUMERIC_SWAP_PENALTY;
@@ -323,18 +337,26 @@ function scoreToken(token: string, index: SearchIndex): FuzzyMatch {
323
337
  return best;
324
338
  }
325
339
 
326
- /** A query normalized and split once, so `fuzzyRank` doesn't re-normalize the
327
- * same query for every candidate in the list. */
340
+ /** A query normalized and split once, so ranking doesn't re-normalize the
341
+ * same query (or rebuild its alphanumeric-swap variants) for every candidate. */
328
342
  interface PreparedQuery {
329
343
  normalized: string;
330
344
  tokens: string[];
345
+ /** `buildAlphanumericSwapQueries` of each token, index-aligned with `tokens`. */
346
+ tokenSwaps: string[][];
331
347
  compact: string;
332
348
  }
333
349
 
334
350
  function prepareQuery(query: string): PreparedQuery | null {
335
351
  const normalized = normalizeForSearch(query);
336
352
  if (normalized.length === 0) return null;
337
- return { normalized, tokens: normalized.split(" "), compact: normalized.replaceAll(" ", "") };
353
+ const tokens = normalized.split(" ");
354
+ return {
355
+ normalized,
356
+ tokens,
357
+ tokenSwaps: tokens.map(buildAlphanumericSwapQueries),
358
+ compact: normalized.replaceAll(" ", ""),
359
+ };
338
360
  }
339
361
 
340
362
  function fuzzyMatchCore(pq: PreparedQuery | null, index: SearchIndex): FuzzyMatch {
@@ -359,8 +381,8 @@ function fuzzyMatchCore(pq: PreparedQuery | null, index: SearchIndex): FuzzyMatc
359
381
  totalScore += compactPhraseIndex * 0.01;
360
382
  }
361
383
 
362
- for (const token of pq.tokens) {
363
- const match = scoreToken(token, index);
384
+ for (let i = 0; i < pq.tokens.length; i++) {
385
+ const match = scoreToken(pq.tokens[i]!, pq.tokenSwaps[i]!, index);
364
386
  if (!match.matches) {
365
387
  return { matches: false, score: 0 };
366
388
  }
@@ -376,6 +398,33 @@ export function fuzzyMatch(query: string, text: string): FuzzyMatch {
376
398
  return fuzzyMatchCore(pq, buildSearchIndex(text));
377
399
  }
378
400
 
401
+ /**
402
+ * A query normalized and tokenized once for matching against many texts.
403
+ *
404
+ * `fuzzyMatch(query, text)` re-normalizes `query` (and rebuilds its
405
+ * alphanumeric-swap variants) on every call; filters that test one query
406
+ * against every row should build one `FuzzyQuery` per keystroke instead.
407
+ * `query.match(text)` returns exactly what `fuzzyMatch(query, text)` returns.
408
+ */
409
+ export class FuzzyQuery {
410
+ readonly #prepared: PreparedQuery | null;
411
+
412
+ constructor(query: string) {
413
+ this.#prepared = prepareQuery(query);
414
+ }
415
+
416
+ /** Same result as `fuzzyMatch(query, text)`; `text` may be a prepared {@link FuzzyText}. */
417
+ match(text: string | FuzzyText): FuzzyMatch {
418
+ const pq = this.#prepared;
419
+ if (pq === null) return { matches: true, score: 0 };
420
+ return fuzzyMatchCore(pq, typeof text === "string" ? buildSearchIndex(text) : fuzzyTextIndex(text));
421
+ }
422
+ }
423
+
424
+ // Module-private read access to `FuzzyText#index` for `FuzzyQuery.match`;
425
+ // assigned by FuzzyText's static initializer.
426
+ let fuzzyTextIndex: (text: FuzzyText) => SearchIndex;
427
+
379
428
  /**
380
429
  * A text prepared once for repeated fuzzy matching.
381
430
  *
@@ -393,10 +442,57 @@ export class FuzzyText {
393
442
  this.#index = buildUncachedSearchIndex(text);
394
443
  }
395
444
 
445
+ static {
446
+ fuzzyTextIndex = text => text.#index;
447
+ }
448
+
396
449
  /** Match `query` (space-separated tokens; all must match) against the prepared text. */
397
- match(query: string): FuzzyMatch {
398
- return fuzzyMatchCore(prepareQuery(query), this.#index);
450
+ match(query: string | FuzzyQuery): FuzzyMatch {
451
+ return typeof query === "string" ? fuzzyMatchCore(prepareQuery(query), this.#index) : query.match(this);
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Items prepared once for repeated {@link fuzzyRank}-equivalent ranking.
457
+ *
458
+ * `fuzzyRank` resolves each candidate's search index through the bounded module
459
+ * cache, which holds at most {@link INDEX_CACHE_MAX} entries; larger candidate
460
+ * lists (the full model catalog) rebuild the overflow on every query. Hold one
461
+ * corpus per stable candidate list and call {@link rank} per keystroke instead.
462
+ */
463
+ export class FuzzyCorpus<T> {
464
+ readonly #items: readonly T[];
465
+ readonly #indexes: SearchIndex[];
466
+
467
+ constructor(items: readonly T[], getText: (item: T) => string) {
468
+ this.#items = items;
469
+ this.#indexes = items.map(item => buildUncachedSearchIndex(getText(item)));
470
+ }
471
+
472
+ /** Same results as `fuzzyRank(items, query, getText)` for the prepared items. */
473
+ rank(query: string): FuzzyFilterResult<T>[] {
474
+ if (!query.trim()) return this.#items.map(item => ({ item, score: 0 }));
475
+ const indexes = this.#indexes;
476
+ return rankIndexed(this.#items, prepareQuery(query), i => indexes[i]!);
477
+ }
478
+ }
479
+
480
+ function rankIndexed<T>(
481
+ items: readonly T[],
482
+ pq: PreparedQuery | null,
483
+ indexAt: (i: number) => SearchIndex,
484
+ ): FuzzyFilterResult<T>[] {
485
+ const results: FuzzyFilterResult<T>[] = [];
486
+ for (let i = 0; i < items.length; i++) {
487
+ const index = indexAt(i);
488
+ const match = pq === null ? { matches: true, score: 0 } : fuzzyMatchCore(pq, index);
489
+ if (match.matches) {
490
+ results.push({ item: items[i]!, score: match.score });
491
+ }
399
492
  }
493
+
494
+ results.sort((a, b) => a.score - b.score);
495
+ return results;
400
496
  }
401
497
 
402
498
  /**
@@ -411,18 +507,7 @@ export function fuzzyRank<T>(items: readonly T[], query: string, getText: (item:
411
507
  // A non-blank query that normalizes to empty (pure punctuation) matches
412
508
  // everything with score 0, but still calls getText per item — consumers rely
413
509
  // on its side effects (see fuzzy-cache.test.ts).
414
- const pq = prepareQuery(query);
415
- const results: FuzzyFilterResult<T>[] = [];
416
- for (const item of items) {
417
- const text = getText(item);
418
- const match = pq === null ? { matches: true, score: 0 } : fuzzyMatchCore(pq, buildSearchIndex(text));
419
- if (match.matches) {
420
- results.push({ item, score: match.score });
421
- }
422
- }
423
-
424
- results.sort((a, b) => a.score - b.score);
425
- return results;
510
+ return rankIndexed(items, prepareQuery(query), i => buildSearchIndex(getText(items[i]!), i < INDEX_CACHE_MAX));
426
511
  }
427
512
 
428
513
  export function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[] {
@@ -62,6 +62,7 @@ function hotkeyGroups(bindings: HotkeysMarkdownBindings): HotkeyGroup[] {
62
62
  if (keys.length > 0) return { keys: keys.map(formatKeyHint), ids: keys, action };
63
63
  return { keys: [bindings.keybindings.getDisplayString(action) || "Disabled"], ids: [], action };
64
64
  };
65
+ const pushToTalkKeys = bindings.keybindings.getKeys("app.stt.pushToTalk");
65
66
  // CustomEditor tests the chord that was actually pressed, so exit keys split by role: a key
66
67
  // that also carries tui.editor.deleteCharForward (the readline `^D` overlap) forward-deletes
67
68
  // while the prompt holds a draft, any other exit key quits immediately. Mixed bindings such as
@@ -139,8 +140,14 @@ function hotkeyGroups(bindings: HotkeysMarkdownBindings): HotkeyGroup[] {
139
140
  { keys: [act("app.retry")], action: "Retry last failed assistant turn" },
140
141
  { keys: [act("app.clipboard.pasteImage")], action: "Paste image or text from clipboard" },
141
142
  {
142
- keys: [{ text: "Hold " }, hints(["space"])],
143
- action: "Speech-to-text (push-to-talk): hold to record, release to transcribe",
143
+ keys:
144
+ pushToTalkKeys.length > 0
145
+ ? [{ text: "Hold " }, act("app.stt.pushToTalk")]
146
+ : [act("app.stt.pushToTalk")],
147
+ action:
148
+ pushToTalkKeys.length > 0
149
+ ? "Speech-to-text (push-to-talk): hold to record, release to transcribe"
150
+ : "Speech-to-text (push-to-talk) (disabled)",
144
151
  },
145
152
  { keys: [act("app.live.toggle")], action: "Start/stop live voice mode (/live)" },
146
153
  {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Key-hint formatting, kept free of the native addon: it imports only types
3
- * plus the theme symbol mirror, so addon-free CLI paths (`omp --version`,
3
+ * plus the theme symbol mirror, so addon-free CLI paths (`zetacode --version`,
4
4
  * help text in cli/command-help.ts) can format keys without loading
5
5
  * `@linxiraos/pi-natives` through the keybindings registry.
6
6
  */
@@ -23,6 +23,14 @@ export type KeyName = KeyId | ModifierName;
23
23
  * is capitalized. Other keys are capitalized (`f5` → `F5`).
24
24
  */
25
25
  export declare function formatKeyHint(key: KeyName): string;
26
+ /**
27
+ * Format one key for a native tooltip (a TSP `title`): unicode keycap glyphs
28
+ * whatever the theme's symbol preset (`shift+tab` → `⇧⇥`), which Tern's
29
+ * tooltip splits into keycaps, and `esc` as Tern's own keycaps read. The nerd
30
+ * preset's icons are missing from Tern's UI font, and its spaced chords would
31
+ * not parse as keys.
32
+ */
33
+ export declare function formatTooltipKey(key: KeyName): string;
26
34
  /** Format alternative keys as a slash-separated hint (`["f5", "alt+r"]` → `F5/⌥R`). */
27
35
  export declare function formatKeyHints(keys: KeyName | readonly KeyName[]): string;
28
36
  /** Format a double-tap gesture: `←←` with glyphs, `Left Left` with words. */
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Key-hint formatting, kept free of the native addon: it imports only types
3
- * plus the theme symbol mirror, so addon-free CLI paths (`omp --version`,
3
+ * plus the theme symbol mirror, so addon-free CLI paths (`zetacode --version`,
4
4
  * help text in cli/command-help.ts) can format keys without loading
5
5
  * `@linxiraos/pi-natives` through the keybindings registry.
6
6
  */
7
7
  import type { KeyId } from "./keybindings";
8
8
  import type { ModifierName } from "./keys";
9
9
  import { activeThemeSymbol } from "./theme/active-symbols";
10
- import type { SymbolKey } from "./theme/symbols";
10
+ import { SYMBOL_PRESETS, type SymbolKey } from "./theme/symbols";
11
11
 
12
12
  /**
13
13
  * Key hint formatting: every key shown to the user renders through
@@ -69,9 +69,8 @@ const KEY_WORDS: Record<string, string | undefined> = {
69
69
  insert: "Ins",
70
70
  };
71
71
 
72
- function keySymbol(key: SymbolKey): string {
73
- return activeThemeSymbol(key);
74
- }
72
+ /** Where a key's glyphs come from: the active theme, or a fixed preset. */
73
+ type KeySymbols = (key: SymbolKey) => string;
75
74
 
76
75
  const HAS_LETTER = /[A-Za-z0-9]/;
77
76
 
@@ -89,6 +88,22 @@ export type KeyName = KeyId | ModifierName;
89
88
  * is capitalized. Other keys are capitalized (`f5` → `F5`).
90
89
  */
91
90
  export function formatKeyHint(key: KeyName): string {
91
+ return formatKey(key, activeThemeSymbol);
92
+ }
93
+
94
+ /**
95
+ * Format one key for a native tooltip (a TSP `title`): unicode keycap glyphs
96
+ * whatever the theme's symbol preset (`shift+tab` → `⇧⇥`), which Tern's
97
+ * tooltip splits into keycaps, and `esc` as Tern's own keycaps read. The nerd
98
+ * preset's icons are missing from Tern's UI font, and its spaced chords would
99
+ * not parse as keys.
100
+ */
101
+ export function formatTooltipKey(key: KeyName): string {
102
+ return key === "escape" ? "esc" : formatKey(key, symbol => SYMBOL_PRESETS.unicode[symbol]);
103
+ }
104
+
105
+ /** {@link formatKeyHint} with glyphs from `symbol`. */
106
+ function formatKey(key: KeyName, symbol: KeySymbols): string {
92
107
  const mac = keyHintPlatform() === "darwin";
93
108
  // A trailing `+` is the plus key itself (`+`, `ctrl++`), not a separator.
94
109
  const parts = key.endsWith("+") ? [...key.slice(0, -1).split("+").slice(0, -1), "+"] : key.split("+");
@@ -96,14 +111,14 @@ export function formatKeyHint(key: KeyName): string {
96
111
  let out = "";
97
112
  for (const modifier in MODIFIER_SYMBOLS) {
98
113
  if (!parts.includes(modifier)) continue;
99
- const label = keySymbol(MODIFIER_SYMBOLS[modifier]![mac ? 1 : 0]);
100
- out += HAS_LETTER.test(label) ? `${label}+` : label + keySymbol("key.joiner");
114
+ const label = symbol(MODIFIER_SYMBOLS[modifier]![mac ? 1 : 0]);
115
+ out += HAS_LETTER.test(label) ? `${label}+` : label + symbol("key.joiner");
101
116
  }
102
117
  const lower = base.toLowerCase();
103
118
  const modifier = MODIFIER_SYMBOLS[lower];
104
- const symbol = KEY_SYMBOLS[lower];
105
- if (modifier) out += keySymbol(modifier[mac ? 1 : 0]);
106
- else if (symbol !== undefined) out += keySymbol(symbol);
119
+ const glyph = KEY_SYMBOLS[lower];
120
+ if (modifier) out += symbol(modifier[mac ? 1 : 0]);
121
+ else if (glyph !== undefined) out += symbol(glyph);
107
122
  else if (KEY_WORDS[lower] !== undefined) out += KEY_WORDS[lower];
108
123
  else if (base.length === 1) out += parts.length === 0 ? base : base.toUpperCase();
109
124
  else out += base[0]!.toUpperCase() + base.slice(1);
@@ -118,5 +133,5 @@ export function formatKeyHints(keys: KeyName | readonly KeyName[]): string {
118
133
  /** Format a double-tap gesture: `←←` with glyphs, `Left Left` with words. */
119
134
  export function formatDoubleTap(key: KeyName): string {
120
135
  const label = formatKeyHint(key);
121
- return `${label}${HAS_LETTER.test(label) ? " " : keySymbol("key.joiner")}${label}`;
136
+ return `${label}${HAS_LETTER.test(label) ? " " : activeThemeSymbol("key.joiner")}${label}`;
122
137
  }
@@ -1,3 +1,4 @@
1
+ import type { KeyId } from "./app-keybindings";
1
2
  /**
2
3
  * Match the coding-agent interrupt key.
3
4
  *
@@ -18,6 +19,8 @@ export declare function matchesSelectDown(data: string): boolean;
18
19
  export declare function matchesSelectPageUp(data: string): boolean;
19
20
  /** Match the generic selector page-down keybinding. */
20
21
  export declare function matchesSelectPageDown(data: string): boolean;
22
+ /** Effective external-editor key: the first bound key, or Ctrl+G when unbound. */
23
+ export declare function appExternalEditorKey(): KeyId;
21
24
  /** Match the external-editor keybinding, falling back to Ctrl+G when unbound. */
22
25
  export declare function matchesAppExternalEditor(data: string): boolean;
23
26
  /**