@linxiraos/pi-tui 1.1.27 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/CHANGELOG.md +47 -463
  2. package/README.md +7 -5
  3. package/dist/types/app-keybindings.d.ts +1 -1
  4. package/dist/types/apps/cleanse-board.d.ts +1 -1
  5. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  6. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  7. package/dist/types/apps/git/git-tui.d.ts +2 -2
  8. package/dist/types/apps/git/help.d.ts +3 -1
  9. package/dist/types/apps/if-bench-board.d.ts +2 -2
  10. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  11. package/dist/types/autocomplete.d.ts +5 -2
  12. package/dist/types/charts/chart-plan.d.ts +674 -0
  13. package/dist/types/charts/chart-svg.d.ts +59 -0
  14. package/dist/types/charts/table-data.d.ts +175 -0
  15. package/dist/types/chat/assistant-message.d.ts +5 -0
  16. package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
  17. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  18. package/dist/types/chat/execution-shared.d.ts +6 -5
  19. package/dist/types/chat/fence-figure.d.ts +42 -0
  20. package/dist/types/chat/figure-markdown.d.ts +42 -0
  21. package/dist/types/chat/messages.d.ts +1 -1
  22. package/dist/types/chat/svg-figure.d.ts +33 -0
  23. package/dist/types/chat/svg-source.d.ts +49 -0
  24. package/dist/types/chat/table-chart.d.ts +89 -0
  25. package/dist/types/chat/tool-execution.d.ts +2 -0
  26. package/dist/types/chat/user-message.d.ts +15 -2
  27. package/dist/types/chrome/shared.d.ts +9 -0
  28. package/dist/types/chrome/transcript-container.d.ts +6 -0
  29. package/dist/types/components/box.d.ts +1 -0
  30. package/dist/types/components/disclosure.d.ts +1 -0
  31. package/dist/types/components/editor.d.ts +15 -4
  32. package/dist/types/components/form.d.ts +1 -0
  33. package/dist/types/components/image.d.ts +12 -44
  34. package/dist/types/components/key-value-list.d.ts +1 -0
  35. package/dist/types/components/layout/row.d.ts +1 -0
  36. package/dist/types/components/layout/stack.d.ts +1 -0
  37. package/dist/types/components/markdown.d.ts +36 -3
  38. package/dist/types/components/scroll-view.d.ts +1 -0
  39. package/dist/types/components/section.d.ts +1 -0
  40. package/dist/types/components/table.d.ts +1 -0
  41. package/dist/types/components/text.d.ts +1 -0
  42. package/dist/types/components/tree-view.d.ts +7 -0
  43. package/dist/types/components/truncated-text.d.ts +1 -0
  44. package/dist/types/fuzzy.d.ts +29 -1
  45. package/dist/types/key-hint-format.d.ts +1 -1
  46. package/dist/types/keybinding-matchers.d.ts +3 -0
  47. package/dist/types/keybindings.d.ts +1 -1
  48. package/dist/types/kitty-graphics.d.ts +2 -2
  49. package/dist/types/loop-watchdog.d.ts +8 -7
  50. package/dist/types/multiplexers/cmux.d.ts +16 -0
  51. package/dist/types/multiplexers/herdr.d.ts +26 -0
  52. package/dist/types/multiplexers/notify.d.ts +13 -0
  53. package/dist/types/multiplexers/orca.d.ts +12 -0
  54. package/dist/types/multiplexers/screen.d.ts +9 -0
  55. package/dist/types/multiplexers/tmux.d.ts +29 -0
  56. package/dist/types/multiplexers/types.d.ts +100 -0
  57. package/dist/types/multiplexers/wmux.d.ts +12 -0
  58. package/dist/types/multiplexers/zellij.d.ts +12 -0
  59. package/dist/types/native/backend.d.ts +3 -2
  60. package/dist/types/native/blobs.d.ts +7 -3
  61. package/dist/types/native/encode.d.ts +3 -4
  62. package/dist/types/native/overlay.d.ts +5 -0
  63. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  64. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  65. package/dist/types/overlays/agent-hub.d.ts +2 -0
  66. package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -2
  67. package/dist/types/overlays/agents-hub.d.ts +9 -0
  68. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  69. package/dist/types/overlays/annotation-types.d.ts +2 -0
  70. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  71. package/dist/types/overlays/history-search.d.ts +10 -0
  72. package/dist/types/overlays/hook-selector.d.ts +9 -4
  73. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  74. package/dist/types/overlays/model-browser.d.ts +54 -7
  75. package/dist/types/overlays/model-hub.d.ts +23 -0
  76. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  77. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  78. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  79. package/dist/types/overlays/session-selector.d.ts +6 -2
  80. package/dist/types/overlays/settings-defs.d.ts +1 -0
  81. package/dist/types/overlays/usage-dashboard.d.ts +8 -4
  82. package/dist/types/overlays/usage-display.d.ts +3 -1
  83. package/dist/types/prompt/composer.d.ts +1 -1
  84. package/dist/types/prompt/custom-editor.d.ts +11 -8
  85. package/dist/types/prompt/image-source.d.ts +2 -1
  86. package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
  87. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  88. package/dist/types/prompt/welcome.d.ts +6 -1
  89. package/dist/types/render/composite.d.ts +2 -0
  90. package/dist/types/render/output-pane.d.ts +1 -0
  91. package/dist/types/render/render-utils.d.ts +1 -0
  92. package/dist/types/render/tool-card.d.ts +6 -0
  93. package/dist/types/render/width-aware-text.d.ts +2 -0
  94. package/dist/types/setup/scenes/types.d.ts +1 -0
  95. package/dist/types/status-line/component.d.ts +3 -0
  96. package/dist/types/status-line/host.d.ts +4 -1
  97. package/dist/types/status-line/segments.d.ts +2 -0
  98. package/dist/types/status-line/types.d.ts +14 -3
  99. package/dist/types/terminal-capabilities.d.ts +27 -35
  100. package/dist/types/terminal-multiplexer.d.ts +127 -14
  101. package/dist/types/terminal.d.ts +22 -6
  102. package/dist/types/theme/active-symbols.d.ts +1 -1
  103. package/dist/types/theme/schema.d.ts +11 -2
  104. package/dist/types/theme/symbols.d.ts +1 -1
  105. package/dist/types/theme/theme-class.d.ts +7 -0
  106. package/dist/types/theme/theme.d.ts +1 -1
  107. package/dist/types/theme/tui-adapters.d.ts +3 -0
  108. package/dist/types/tmux.d.ts +11 -4
  109. package/dist/types/tools/daemon.d.ts +1 -1
  110. package/dist/types/tools/eval.d.ts +22 -3
  111. package/dist/types/tools/glob.d.ts +4 -0
  112. package/dist/types/tools/native-view.d.ts +47 -0
  113. package/dist/types/tools/output-meta.d.ts +11 -1
  114. package/dist/types/tools/renderer.d.ts +37 -2
  115. package/dist/types/tools/streaming-output.d.ts +20 -1
  116. package/dist/types/tools/task.d.ts +0 -4
  117. package/dist/types/tools/write.d.ts +9 -2
  118. package/dist/types/tools/yield.d.ts +22 -0
  119. package/dist/types/tui.d.ts +31 -0
  120. package/dist/types/utils.d.ts +6 -0
  121. package/dist/types/vim.d.ts +19 -5
  122. package/package.json +13 -9
  123. package/src/app-keybindings.d.ts +1 -1
  124. package/src/app-keybindings.ts +1 -1
  125. package/src/apps/autoresearch-dashboard.ts +7 -3
  126. package/src/apps/cleanse-board.ts +2 -2
  127. package/src/apps/cleanse-picker.ts +1 -1
  128. package/src/apps/debug/log-viewer.ts +188 -72
  129. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  130. package/src/apps/debug/raw-sse.ts +39 -5
  131. package/src/apps/git/diff-pane.ts +26 -5
  132. package/src/apps/git/git-tui.ts +2 -2
  133. package/src/apps/git/help.ts +29 -20
  134. package/src/apps/if-bench-board.ts +2 -2
  135. package/src/apps/ps-top.ts +2 -2
  136. package/src/apps/setup-model-picker.ts +1 -1
  137. package/src/autocomplete.d.ts +5 -2
  138. package/src/autocomplete.ts +67 -43
  139. package/src/charts/chart-plan.d.ts +674 -0
  140. package/src/charts/chart-plan.ts +5081 -0
  141. package/src/charts/chart-svg.d.ts +59 -0
  142. package/src/charts/chart-svg.ts +4657 -0
  143. package/src/charts/table-data.d.ts +175 -0
  144. package/src/charts/table-data.ts +1478 -0
  145. package/src/chat/advisor-message.ts +11 -0
  146. package/src/chat/assistant-message.d.ts +5 -0
  147. package/src/chat/assistant-message.ts +219 -50
  148. package/src/chat/chat-transcript-builder.d.ts +2 -0
  149. package/src/chat/chat-transcript-builder.ts +5 -3
  150. package/src/chat/compaction-summary-message.d.ts +1 -0
  151. package/src/chat/compaction-summary-message.ts +8 -0
  152. package/src/chat/execution-shared.d.ts +6 -5
  153. package/src/chat/execution-shared.ts +42 -25
  154. package/src/chat/fence-figure.d.ts +42 -0
  155. package/src/chat/fence-figure.ts +80 -0
  156. package/src/chat/figure-markdown.d.ts +42 -0
  157. package/src/chat/figure-markdown.ts +174 -0
  158. package/src/chat/messages.d.ts +1 -1
  159. package/src/chat/messages.ts +1 -1
  160. package/src/chat/read-tool-group.ts +6 -4
  161. package/src/chat/skill-message.ts +6 -0
  162. package/src/chat/svg-figure.d.ts +33 -0
  163. package/src/chat/svg-figure.ts +399 -0
  164. package/src/chat/svg-source.d.ts +49 -0
  165. package/src/chat/svg-source.ts +188 -0
  166. package/src/chat/table-chart.d.ts +89 -0
  167. package/src/chat/table-chart.ts +294 -0
  168. package/src/chat/tool-execution.d.ts +2 -0
  169. package/src/chat/tool-execution.ts +80 -6
  170. package/src/chat/user-message.d.ts +15 -2
  171. package/src/chat/user-message.ts +55 -3
  172. package/src/chrome/shared.ts +53 -0
  173. package/src/chrome/transcript-container.d.ts +6 -0
  174. package/src/chrome/transcript-container.ts +93 -13
  175. package/src/components/box.d.ts +1 -0
  176. package/src/components/box.ts +5 -0
  177. package/src/components/disclosure.d.ts +1 -0
  178. package/src/components/disclosure.ts +8 -0
  179. package/src/components/editor.d.ts +15 -4
  180. package/src/components/editor.ts +294 -46
  181. package/src/components/form.d.ts +1 -0
  182. package/src/components/form.ts +8 -0
  183. package/src/components/image.d.ts +12 -44
  184. package/src/components/image.ts +47 -151
  185. package/src/components/key-value-list.d.ts +1 -0
  186. package/src/components/key-value-list.ts +4 -0
  187. package/src/components/layout/row.d.ts +1 -0
  188. package/src/components/layout/row.ts +7 -0
  189. package/src/components/layout/stack.d.ts +1 -0
  190. package/src/components/layout/stack.ts +7 -0
  191. package/src/components/markdown.d.ts +36 -3
  192. package/src/components/markdown.ts +433 -318
  193. package/src/components/scroll-view.d.ts +1 -0
  194. package/src/components/scroll-view.ts +5 -0
  195. package/src/components/section.d.ts +1 -0
  196. package/src/components/section.ts +5 -0
  197. package/src/components/settings-list.ts +2 -0
  198. package/src/components/table.d.ts +1 -0
  199. package/src/components/table.ts +4 -0
  200. package/src/components/text.d.ts +1 -0
  201. package/src/components/text.ts +7 -0
  202. package/src/components/tree-view.d.ts +7 -0
  203. package/src/components/tree-view.ts +38 -8
  204. package/src/components/truncated-text.d.ts +1 -0
  205. package/src/components/truncated-text.ts +5 -0
  206. package/src/desktop-notify.ts +1 -1
  207. package/src/fuzzy.d.ts +29 -1
  208. package/src/fuzzy.ts +115 -30
  209. package/src/key-hint-format.d.ts +1 -1
  210. package/src/key-hint-format.ts +1 -1
  211. package/src/keybinding-matchers.d.ts +3 -0
  212. package/src/keybinding-matchers.ts +6 -2
  213. package/src/keybindings.d.ts +1 -1
  214. package/src/keybindings.ts +1 -1
  215. package/src/kitty-graphics.d.ts +2 -2
  216. package/src/kitty-graphics.ts +4 -4
  217. package/src/loop-watchdog.d.ts +8 -7
  218. package/src/loop-watchdog.ts +18 -13
  219. package/src/multiplexers/cmux.d.ts +16 -0
  220. package/src/multiplexers/cmux.ts +29 -0
  221. package/src/multiplexers/herdr.d.ts +26 -0
  222. package/src/multiplexers/herdr.ts +64 -0
  223. package/src/multiplexers/notify.d.ts +13 -0
  224. package/src/multiplexers/notify.ts +24 -0
  225. package/src/multiplexers/orca.d.ts +12 -0
  226. package/src/multiplexers/orca.ts +16 -0
  227. package/src/multiplexers/screen.d.ts +9 -0
  228. package/src/multiplexers/screen.ts +15 -0
  229. package/src/multiplexers/tmux.d.ts +29 -0
  230. package/src/multiplexers/tmux.ts +40 -0
  231. package/src/multiplexers/types.d.ts +100 -0
  232. package/src/multiplexers/types.ts +110 -0
  233. package/src/multiplexers/wmux.d.ts +12 -0
  234. package/src/multiplexers/wmux.ts +16 -0
  235. package/src/multiplexers/zellij.d.ts +12 -0
  236. package/src/multiplexers/zellij.ts +24 -0
  237. package/src/native/backend.d.ts +3 -2
  238. package/src/native/backend.ts +174 -18
  239. package/src/native/blobs.d.ts +7 -3
  240. package/src/native/blobs.ts +47 -12
  241. package/src/native/encode.d.ts +3 -4
  242. package/src/native/encode.ts +58 -31
  243. package/src/native/overlay.d.ts +5 -0
  244. package/src/native/overlay.ts +13 -0
  245. package/src/native/reconcile.ts +4 -0
  246. package/src/overlays/agent-hub-projection.d.ts +6 -5
  247. package/src/overlays/agent-hub-projection.ts +124 -13
  248. package/src/overlays/agent-hub-types.d.ts +9 -0
  249. package/src/overlays/agent-hub-types.ts +6 -0
  250. package/src/overlays/agent-hub.ts +44 -29
  251. package/src/overlays/agent-transcript-viewer.ts +91 -23
  252. package/src/overlays/agents-hub.ts +28 -8
  253. package/src/overlays/annotation-overlay.ts +293 -90
  254. package/src/overlays/annotation-types.ts +2 -0
  255. package/src/overlays/ask-dialog.ts +7 -10
  256. package/src/overlays/btw-history-panel.ts +12 -1
  257. package/src/overlays/copy-selector.ts +30 -8
  258. package/src/overlays/extensions/state-manager.ts +52 -12
  259. package/src/overlays/history-search.ts +100 -7
  260. package/src/overlays/hook-selector.ts +12 -5
  261. package/src/overlays/jobs-panel.ts +28 -46
  262. package/src/overlays/model-browser.ts +341 -110
  263. package/src/overlays/model-hub.ts +300 -49
  264. package/src/overlays/move-overlay.ts +28 -14
  265. package/src/overlays/oauth-selector.ts +54 -70
  266. package/src/overlays/plan-review-overlay.ts +194 -70
  267. package/src/overlays/plugin-settings.ts +3 -3
  268. package/src/overlays/report-panel.ts +2 -2
  269. package/src/overlays/rewind-selector.ts +86 -17
  270. package/src/overlays/session-observer-registry.d.ts +2 -0
  271. package/src/overlays/session-observer-registry.ts +10 -2
  272. package/src/overlays/session-selector.ts +64 -26
  273. package/src/overlays/settings-defs.ts +4 -3
  274. package/src/overlays/settings-selector.ts +33 -8
  275. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  276. package/src/overlays/stats-notice.ts +1 -1
  277. package/src/overlays/tree-selector.ts +41 -20
  278. package/src/overlays/usage-dashboard.ts +100 -50
  279. package/src/overlays/usage-display.ts +10 -4
  280. package/src/prompt/attachment-chips.ts +15 -11
  281. package/src/prompt/composer-attachments.ts +6 -4
  282. package/src/prompt/composer.ts +4 -15
  283. package/src/prompt/custom-editor.ts +54 -26
  284. package/src/prompt/image-source.ts +2 -1
  285. package/src/prompt/interactive-context-helpers.ts +30 -18
  286. package/src/prompt/macos-spelling.ts +7 -0
  287. package/src/prompt/magic-keywords.ts +11 -2
  288. package/src/prompt/model-mention-autocomplete.ts +14 -14
  289. package/src/prompt/tips.txt +6 -5
  290. package/src/prompt/welcome.ts +55 -12
  291. package/src/prompt/word-completion.ts +38 -5
  292. package/src/render/composite.d.ts +2 -0
  293. package/src/render/composite.ts +64 -0
  294. package/src/render/output-pane.d.ts +1 -0
  295. package/src/render/output-pane.ts +6 -0
  296. package/src/render/render-utils.d.ts +1 -0
  297. package/src/render/render-utils.ts +11 -0
  298. package/src/render/tool-card.d.ts +6 -0
  299. package/src/render/tool-card.ts +15 -0
  300. package/src/render/width-aware-text.d.ts +2 -0
  301. package/src/render/width-aware-text.ts +5 -0
  302. package/src/setup/scenes/model.ts +2 -1
  303. package/src/setup/scenes/types.ts +1 -0
  304. package/src/status-line/component.ts +292 -78
  305. package/src/status-line/host.ts +4 -1
  306. package/src/status-line/segments.ts +40 -22
  307. package/src/status-line/types.ts +15 -3
  308. package/src/terminal-capabilities.d.ts +27 -35
  309. package/src/terminal-capabilities.ts +75 -189
  310. package/src/terminal-multiplexer.d.ts +127 -14
  311. package/src/terminal-multiplexer.ts +112 -33
  312. package/src/terminal.d.ts +22 -6
  313. package/src/terminal.ts +139 -37
  314. package/src/theme/active-symbols.d.ts +1 -1
  315. package/src/theme/active-symbols.ts +1 -1
  316. package/src/theme/schema-validation.ts +1 -0
  317. package/src/theme/schema.d.ts +11 -2
  318. package/src/theme/schema.ts +14 -1
  319. package/src/theme/session-color.ts +1 -7
  320. package/src/theme/symbols.d.ts +1 -1
  321. package/src/theme/symbols.ts +6 -1
  322. package/src/theme/theme-class.d.ts +7 -0
  323. package/src/theme/theme-class.ts +26 -1
  324. package/src/theme/theme-schema.json +5 -1
  325. package/src/theme/theme.d.ts +1 -1
  326. package/src/theme/theme.ts +8 -4
  327. package/src/theme/tui-adapters.d.ts +3 -0
  328. package/src/theme/tui-adapters.ts +68 -0
  329. package/src/tmux.d.ts +11 -4
  330. package/src/tmux.ts +47 -11
  331. package/src/tools/ast-edit.ts +59 -35
  332. package/src/tools/ast-grep.ts +35 -16
  333. package/src/tools/bash.ts +92 -42
  334. package/src/tools/daemon.d.ts +1 -1
  335. package/src/tools/daemon.ts +1 -1
  336. package/src/tools/default-renderer.ts +35 -19
  337. package/src/tools/edit.ts +142 -120
  338. package/src/tools/eval-format/index.ts +28 -2
  339. package/src/tools/eval-format/javascript.ts +7 -5
  340. package/src/tools/eval.d.ts +22 -3
  341. package/src/tools/eval.ts +364 -208
  342. package/src/tools/fetch.ts +5 -6
  343. package/src/tools/glob.d.ts +4 -0
  344. package/src/tools/glob.ts +33 -65
  345. package/src/tools/grep.ts +81 -48
  346. package/src/tools/index.ts +2 -0
  347. package/src/tools/lsp.ts +81 -59
  348. package/src/tools/mcp.ts +111 -68
  349. package/src/tools/native-view.d.ts +47 -0
  350. package/src/tools/native-view.ts +102 -1
  351. package/src/tools/output-meta.d.ts +11 -1
  352. package/src/tools/output-meta.ts +23 -3
  353. package/src/tools/read.ts +7 -5
  354. package/src/tools/renderer.d.ts +37 -2
  355. package/src/tools/renderer.ts +42 -2
  356. package/src/tools/streaming-output.d.ts +20 -1
  357. package/src/tools/streaming-output.ts +149 -50
  358. package/src/tools/task.d.ts +0 -4
  359. package/src/tools/task.ts +112 -59
  360. package/src/tools/todo.ts +119 -30
  361. package/src/tools/wait.ts +34 -24
  362. package/src/tools/web-search.ts +1 -1
  363. package/src/tools/write.d.ts +9 -2
  364. package/src/tools/write.ts +147 -95
  365. package/src/tools/yield.d.ts +22 -0
  366. package/src/tools/yield.ts +225 -0
  367. package/src/tui.d.ts +31 -0
  368. package/src/tui.ts +309 -160
  369. package/src/utils.d.ts +6 -0
  370. package/src/utils.ts +19 -0
  371. package/src/vim.d.ts +19 -5
  372. package/src/vim.ts +333 -18
  373. package/src/windows-input-mode.ts +8 -0
@@ -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;
@@ -176,7 +176,7 @@ export function sendDesktopNotification(message: string | TerminalNotification):
176
176
  try {
177
177
  // `.unref()` lets the event loop exit while the notifier is still running.
178
178
  // Without it, an unresponsive D-Bus activation (slow `notify-send`, hung
179
- // `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
180
180
  // the renderer's shutdown — a completion toast must never delay process
181
181
  // exit. Ignored stdio alone does not detach the child from the parent's
182
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[] {
@@ -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
  */
@@ -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
  */
@@ -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
  /**
@@ -53,11 +53,15 @@ export function matchesSelectPageDown(data: string): boolean {
53
53
  return getKeybindings().matches(data, "tui.select.pageDown");
54
54
  }
55
55
 
56
+ /** Effective external-editor key: the first bound key, or Ctrl+G when unbound. */
57
+ export function appExternalEditorKey(): KeyId {
58
+ return getKeybindings().getKeys("app.editor.external")[0] ?? "ctrl+g";
59
+ }
60
+
56
61
  /** Match the external-editor keybinding, falling back to Ctrl+G when unbound. */
57
62
  export function matchesAppExternalEditor(data: string): boolean {
58
63
  const keybindings = getKeybindings();
59
- const externalEditorKeys = keybindings.getKeys("app.editor.external");
60
- if (externalEditorKeys.length > 0) {
64
+ if (keybindings.getKeys("app.editor.external").length > 0) {
61
65
  return keybindings.matches(data, "app.editor.external");
62
66
  }
63
67
  return matchesKey(data, "ctrl+g");
@@ -107,7 +107,7 @@ export declare const TUI_KEYBINDINGS: {
107
107
  readonly description: "Delete word backward";
108
108
  };
109
109
  readonly "tui.editor.deleteWordForward": {
110
- readonly defaultKeys: ["alt+delete", "alt+d", "super+alt+delete", "super+alt+d"];
110
+ readonly defaultKeys: ["alt+delete", "alt+d", "ctrl+delete", "super+alt+delete", "super+alt+d"];
111
111
  readonly description: "Delete word forward";
112
112
  };
113
113
  readonly "tui.editor.deleteToLineStart": {
@@ -105,7 +105,7 @@ export const TUI_KEYBINDINGS = {
105
105
  description: "Delete word backward",
106
106
  },
107
107
  "tui.editor.deleteWordForward": {
108
- defaultKeys: ["alt+delete", "alt+d", "super+alt+delete", "super+alt+d"],
108
+ defaultKeys: ["alt+delete", "alt+d", "ctrl+delete", "super+alt+delete", "super+alt+d"],
109
109
  description: "Delete word forward",
110
110
  },
111
111
  "tui.editor.deleteToLineStart": {
@@ -30,8 +30,8 @@ export interface KittyGraphicsFeatures {
30
30
  * cannot use cursor-positioned placements because the outer terminal does not
31
31
  * know pane scroll/reflow state. An explicit `PI_FORCE_IMAGE_PROTOCOL=kitty`
32
32
  * opts into placeholders under any multiplexer — matching `timg -pk`.
33
- * Automatic Herdr fallback stays off because its pane marker does not prove
34
- * that the experimental Kitty renderer is enabled.
33
+ * Automatic fallback stays off inside multiplexers whose pane marker does not
34
+ * prove that a Kitty renderer is enabled (Herdr's is experimental).
35
35
  *
36
36
  * `PI_NO_KITTY_PLACEHOLDERS=1` and `PI_KITTY_PLACEHOLDERS=0` remain hard
37
37
  * opt-outs; `PI_KITTY_PLACEHOLDERS=1` explicitly opts in anywhere else.
@@ -15,7 +15,7 @@
15
15
  * forms. Protocol gating (`imageProtocol === Kitty`) lives in the caller.
16
16
  */
17
17
 
18
- import { isInsideHerdr, isInsideTerminalMultiplexer } from "./terminal-multiplexer";
18
+ import { isInsideTerminalMultiplexer, terminalMultiplexerSessions } from "./terminal-multiplexer";
19
19
  import { wrapTmuxPassthroughIfNeeded } from "./tmux";
20
20
 
21
21
  /** Kitty Unicode placeholder base character (U+10EEEE, Plane 16 PUA). */
@@ -67,8 +67,8 @@ export interface KittyGraphicsFeatures {
67
67
  * cannot use cursor-positioned placements because the outer terminal does not
68
68
  * know pane scroll/reflow state. An explicit `PI_FORCE_IMAGE_PROTOCOL=kitty`
69
69
  * opts into placeholders under any multiplexer — matching `timg -pk`.
70
- * Automatic Herdr fallback stays off because its pane marker does not prove
71
- * that the experimental Kitty renderer is enabled.
70
+ * Automatic fallback stays off inside multiplexers whose pane marker does not
71
+ * prove that a Kitty renderer is enabled (Herdr's is experimental).
72
72
  *
73
73
  * `PI_NO_KITTY_PLACEHOLDERS=1` and `PI_KITTY_PLACEHOLDERS=0` remain hard
74
74
  * opt-outs; `PI_KITTY_PLACEHOLDERS=1` explicitly opts in anywhere else.
@@ -81,7 +81,7 @@ export function detectKittyUnicodePlaceholdersSupport(terminalId: string, env: N
81
81
  if (force === "0" || force === "false" || force === "off" || force === "no" || force === "n") return false;
82
82
  const insideMultiplexer = isInsideTerminalMultiplexer(env);
83
83
  if (insideMultiplexer && env.PI_FORCE_IMAGE_PROTOCOL?.trim().toLowerCase() === "kitty") return true;
84
- if (isInsideHerdr(env)) return false;
84
+ if (terminalMultiplexerSessions(env).some(multiplexer => multiplexer.imagesRequireOverride)) return false;
85
85
  return (
86
86
  terminalId === "kitty" ||
87
87
  terminalId === "ghostty" ||
@@ -21,13 +21,14 @@ interface LoopWatchdogTimer {
21
21
  cancel?(): void;
22
22
  }
23
23
  /**
24
- * Always-on event-loop lag probe. Each tick is scheduled `intervalMs` ahead of
25
- * a recorded deadline; a tick that fires `thresholdMs` past its deadline means
26
- * the loop was blocked that long. The overshoot is logged once on the rising
27
- * edge (one block ⇒ one line, deduped via `#wasBlocked`), tagged with the phase
28
- * active during the elapsed interval via {@link takeRecentLoopPhase} — which
29
- * survives the synchronous push/pop the instrumented hot paths do before this
30
- * delayed tick can run — so the stall names its cause instead of "unknown".
24
+ * Always-on event-loop lag probe. Each tick is scheduled `intervalMs` ahead,
25
+ * with that target recorded as its deadline; a tick that fires `thresholdMs`
26
+ * past its deadline means the loop was blocked that long. The overshoot is
27
+ * logged once on the rising edge (one block ⇒ one line, deduped via
28
+ * `#wasBlocked`). Attribution via
29
+ * {@link takeLoopPhaseAttribution} totals each innermost phase only after the tick's
30
+ * deadline. A phase is named, with `phaseMs`, only when its total outweighs all
31
+ * unlabeled time in that late window; otherwise the block remains "unknown".
31
32
  *
32
33
  * The handle is `unref`'d so the probe never keeps the process alive, and stop()
33
34
  * cancels the armed timer when the handle exposes `cancel` (the default