@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
@@ -9,7 +9,7 @@ import { actionBar, actionButton } from "../../native/overlay";
9
9
  import { routeSgrMouseInput, type SgrMouseEvent } from "../../mouse";
10
10
  import { truncateToWidth } from "../../utils";
11
11
  import { sanitizeDisplayText } from "../../overlays/extensions/display-text";
12
- import { theme } from "../../theme/theme";
12
+ import { getThemeEpoch, theme } from "../../theme/theme";
13
13
  import { DebugViewerFrame } from "./viewer-frame";
14
14
  import {
15
15
  formatRawSseIsoTime,
@@ -106,6 +106,14 @@ export class RawSseViewerComponent implements Component {
106
106
  // Sequences are monotonic; we prune entries below the oldest live record
107
107
  // after each render so the cache tracks the buffer's eviction window.
108
108
  readonly #prettyLinesCache = new Map<number, string[]>();
109
+ /** Buffer snapshot for {@link #snapshotRevision}; the buffer's revision bumps on every change. */
110
+ #snapshot: RawSseDebugSnapshot | undefined;
111
+ #snapshotRevision = -1;
112
+ /** Rendered body for (buffer revision, width, theme epoch). */
113
+ #bodyLines: string[] = [];
114
+ #bodyRevision = -1;
115
+ #bodyWidth = -1;
116
+ #bodyThemeEpoch = -1;
109
117
 
110
118
  constructor(options: RawSseViewerOptions) {
111
119
  this.#deps = options.deps;
@@ -227,6 +235,7 @@ export class RawSseViewerComponent implements Component {
227
235
  this.#native.clear();
228
236
  this.#nativeHead.clear();
229
237
  this.#nativeBody.clear();
238
+ this.#bodyRevision = -1;
230
239
  }
231
240
 
232
241
  render(width: number): readonly string[] {
@@ -249,7 +258,7 @@ export class RawSseViewerComponent implements Component {
249
258
  * so they never scroll away.
250
259
  */
251
260
  describeScreen(): NativeScreen {
252
- const snapshot = this.#buffer.snapshot();
261
+ const snapshot = this.#currentSnapshot();
253
262
  const stream = this.#feedNative(snapshot);
254
263
  const head = this.#nativeHead.get(
255
264
  [snapshot.totalEvents, snapshot.records.length, snapshot.droppedRecords, snapshot.lastUpdatedAt],
@@ -372,15 +381,40 @@ export class RawSseViewerComponent implements Component {
372
381
  this.#onExit();
373
382
  }
374
383
 
384
+ /** The buffer's snapshot, re-taken only when its revision moved. */
385
+ #currentSnapshot(): RawSseDebugSnapshot {
386
+ const revision = this.#buffer.revision;
387
+ if (!this.#snapshot || this.#snapshotRevision !== revision) {
388
+ this.#snapshot = this.#buffer.snapshot();
389
+ this.#snapshotRevision = revision;
390
+ }
391
+ return this.#snapshot;
392
+ }
393
+
375
394
  #renderRawLines(innerWidth: number): string[] {
376
- const snapshot = this.#buffer.snapshot();
395
+ const snapshot = this.#currentSnapshot();
396
+ const themeEpoch = getThemeEpoch();
397
+ if (
398
+ this.#bodyRevision === this.#snapshotRevision &&
399
+ this.#bodyWidth === innerWidth &&
400
+ this.#bodyThemeEpoch === themeEpoch
401
+ ) {
402
+ return this.#bodyLines;
403
+ }
404
+ this.#bodyRevision = this.#snapshotRevision;
405
+ this.#bodyWidth = innerWidth;
406
+ this.#bodyThemeEpoch = themeEpoch;
407
+ this.#bodyLines = this.#formatRawLines(snapshot, innerWidth);
408
+ return this.#bodyLines;
409
+ }
410
+
411
+ #formatRawLines(snapshot: RawSseDebugSnapshot, innerWidth: number): string[] {
377
412
  if (snapshot.records.length === 0) {
378
413
  return [
379
414
  theme.fg("muted", "No raw SSE frames captured yet."),
380
415
  theme.fg("muted", "HTTP SSE providers populate this view while a model response is streaming."),
381
416
  ];
382
417
  }
383
-
384
418
  const lines: string[] = [];
385
419
  if (snapshot.droppedRecords > 0) {
386
420
  lines.push(
@@ -421,7 +455,7 @@ export class RawSseViewerComponent implements Component {
421
455
  }
422
456
  }
423
457
  #summaryText(): string {
424
- const snapshot = this.#buffer.snapshot();
458
+ const snapshot = this.#currentSnapshot();
425
459
  const last = snapshot.lastUpdatedAt
426
460
  ? `${theme.fg("muted", "last")} ${theme.fg("accent", formatRawSseIsoTime(snapshot.lastUpdatedAt))}`
427
461
  : theme.fg("muted", "waiting for first frame");
@@ -19,7 +19,7 @@ import { centerLine, sliceWithWidth, truncateToWidth, visibleWidth } from "../..
19
19
  import { formatBytes } from "@linxiraos/pi-utils";
20
20
  import { sanitizeDisplayText } from "../../overlays/extensions/display-text";
21
21
  import { getLanguageFromPath } from "../../lang-from-path";
22
- import { createHighlightStream, theme } from "../../theme/theme";
22
+ import { createHighlightStream, getThemeEpoch, theme } from "../../theme/theme";
23
23
  import { bgAnsiHex, canvasHex, fgAnsiHex, mixHex, pill, selectionBgAnsi, textHex, withBg } from "./colors";
24
24
  import { DIFF_CONTEXT_LINES, type FileAssetSide, type FileStreamUpdate } from "./state";
25
25
  import { tuiText } from "../../i18n";
@@ -502,9 +502,12 @@ interface DiffPalette {
502
502
  gutterDel: string;
503
503
  }
504
504
 
505
- let paletteCache: { key: string; palette: DiffPalette } | undefined;
505
+ let paletteCache: { epoch: number; theme: object; palette: DiffPalette } | undefined;
506
506
 
507
+ /** Diff colors for the active theme, rebuilt only when the theme instance or epoch changes. */
507
508
  function palette(): DiffPalette {
509
+ const epoch = getThemeEpoch();
510
+ if (paletteCache?.epoch === epoch && paletteCache.theme === theme) return paletteCache.palette;
508
511
  const added = theme.getColorHex("toolDiffAdded");
509
512
  const removed = theme.getColorHex("toolDiffRemoved");
510
513
  const accent = theme.getColorHex("accent");
@@ -512,8 +515,6 @@ function palette(): DiffPalette {
512
515
  const dark = luminance === undefined || luminance <= 0.5;
513
516
  const canvas = canvasHex();
514
517
  const text = textHex();
515
- const key = `${added}\u0000${removed}\u0000${accent}\u0000${dark}\u0000${canvas}\u0000${text}`;
516
- if (paletteCache?.key === key) return paletteCache.palette;
517
518
  const built: DiffPalette = {
518
519
  addSoft: bgAnsiHex(mixHex(canvas, added, dark ? 0.18 : 0.24)),
519
520
  addStrong: bgAnsiHex(mixHex(canvas, added, dark ? 0.42 : 0.48)),
@@ -529,7 +530,7 @@ function palette(): DiffPalette {
529
530
  gutterAdd: added,
530
531
  gutterDel: removed,
531
532
  };
532
- paletteCache = { key, palette: built };
533
+ paletteCache = { epoch, theme, palette: built };
533
534
  return built;
534
535
  }
535
536
 
@@ -599,6 +600,10 @@ export class DiffPane {
599
600
  #lastHeight = 1;
600
601
  #lastWidth = 0;
601
602
  #layoutCache: { key: string; visuals: Visual[] } | undefined;
603
+ /** Minimap column for (layout, height, scroll offset, palette). */
604
+ #minimapCache:
605
+ | { visuals: Visual[]; height: number; scrollTop: number; colors: DiffPalette; lines: string[] }
606
+ | undefined;
602
607
  /** Per visible row: clickable hunk-button ranges recorded during render. */
603
608
  #hits: ({ hunk: number; primary?: [number, number]; discard?: [number, number] } | undefined)[] = [];
604
609
  /**
@@ -1500,6 +1505,22 @@ export class DiffPane {
1500
1505
  }
1501
1506
 
1502
1507
  #renderMinimap(visuals: Visual[], height: number, colors: DiffPalette): string[] {
1508
+ const cached = this.#minimapCache;
1509
+ if (
1510
+ cached &&
1511
+ cached.visuals === visuals &&
1512
+ cached.height === height &&
1513
+ cached.scrollTop === this.scrollTop &&
1514
+ cached.colors === colors
1515
+ ) {
1516
+ return cached.lines;
1517
+ }
1518
+ const lines = this.#buildMinimap(visuals, height, colors);
1519
+ this.#minimapCache = { visuals, height, scrollTop: this.scrollTop, colors, lines };
1520
+ return lines;
1521
+ }
1522
+
1523
+ #buildMinimap(visuals: Visual[], height: number, colors: DiffPalette): string[] {
1503
1524
  const total = visuals.length;
1504
1525
  const lines: string[] = [];
1505
1526
  const bandKind = (band: number): RowKind | "hunk" | null => {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `omp git` — fullscreen repository TUI.
2
+ * `zetacode git` — fullscreen repository TUI.
3
3
  *
4
4
  * Layout: header (file path, encoding, stage-file button, close), toolbar
5
5
  * (scope chip, file/diff toggle, hunk navigation, hunk/inline/split view
@@ -1235,7 +1235,7 @@ export async function showGitOverlay(ui: TUI, host: GitTuiHost): Promise<void> {
1235
1235
  }
1236
1236
  }
1237
1237
 
1238
- /** Run the fullscreen git TUI standalone (`omp git`) until the user quits. */
1238
+ /** Run the fullscreen git TUI standalone (`zetacode git`) until the user quits. */
1239
1239
  export async function runGitTui(host: GitTuiHost): Promise<void> {
1240
1240
  const ui = new TUI(new ProcessTerminal());
1241
1241
  ui.start();
@@ -11,7 +11,8 @@ import { formatKeyHint } from "../../app-keybindings";
11
11
  import type { KeyName } from "../../key-hint-format";
12
12
  import { matchesKey } from "../../keys";
13
13
  import { col, node, span } from "../../native/describe";
14
- import type { NativeNode } from "../../native/node";
14
+ import type { NativeNode, NativeUiEvent } from "../../native/node";
15
+ import { actionBar, actionButton } from "../../native/overlay";
15
16
  import { theme } from "../../theme/theme";
16
17
  import type { Component } from "../../tui";
17
18
  import { truncateToWidth, visibleWidth } from "../../utils";
@@ -79,28 +80,36 @@ export class GitHelpSheet implements Component {
79
80
  if (data === "?" || data === "q" || matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) this.#close();
80
81
  }
81
82
 
83
+ /** Close runs what Esc runs. */
84
+ handleNativeEvent(event: NativeUiEvent): void {
85
+ if (event.type === "action" && event.act === "close") this.#close();
86
+ }
87
+
82
88
  describe(): NativeNode {
83
89
  this.#native ??= col(
84
- GROUPS.map(group =>
85
- node(
86
- "section",
87
- { head: group.title, role: "zeta.hotkeys.group" },
88
- [
89
- node("table", {
90
- role: "zeta.hotkeys.table",
91
- cols: [
92
- { id: "keys", head: "Keys" },
93
- { id: "does", head: "Action", grow: 1 },
94
- ],
95
- rows: group.rows.map((row, index) => ({
96
- id: String(index),
97
- cells: { keys: row.keys.map(key => span(formatKeyHint(key), "key")), does: row.label },
98
- })),
99
- }),
100
- ],
101
- group.title,
90
+ [
91
+ ...GROUPS.map(group =>
92
+ node(
93
+ "section",
94
+ { head: group.title, role: "zeta.hotkeys.group" },
95
+ [
96
+ node("table", {
97
+ role: "zeta.hotkeys.table",
98
+ cols: [
99
+ { id: "keys", head: "Keys" },
100
+ { id: "does", head: "Action", grow: 1 },
101
+ ],
102
+ rows: group.rows.map((row, index) => ({
103
+ id: String(index),
104
+ cells: { keys: row.keys.map(key => span(formatKeyHint(key), "key")), does: row.label },
105
+ })),
106
+ }),
107
+ ],
108
+ group.title,
109
+ ),
102
110
  ),
103
- ),
111
+ actionBar([null, actionButton("Close", "close", { keys: "escape" })]),
112
+ ],
104
113
  { gap: "lg", role: "zeta.app.git.help" },
105
114
  );
106
115
  return this.#native;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Live board and scoreboard for `omp if-bench`.
2
+ * Live board and scoreboard for `zetacode if-bench`.
3
3
  *
4
4
  * Interactive terminals get one repainted row per model — a turn ladder that
5
5
  * fills as turns pass, the in-flight turn as a spinner cell, and the live
@@ -71,7 +71,7 @@ const FAILURE_TEXT: Record<IfBenchFailure, string> = {
71
71
  provider: "provider error",
72
72
  };
73
73
 
74
- /** Rendering surface for one `omp if-bench` run. */
74
+ /** Rendering surface for one `zetacode if-bench` run. */
75
75
  export interface IfBenchBoard extends IfBenchObserver {
76
76
  readonly interactive: boolean;
77
77
  /** Print a permanent line above the live rows (plain write when non-TTY). */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Interactive alt-screen monitor for `omp ps` (btop idiom): a live process
2
+ * Interactive alt-screen monitor for `zetacode ps` (btop idiom): a live process
3
3
  * table over every selected broker scope with in-place actions.
4
4
  *
5
5
  * Keys — table: `↑/↓`/`j/k` select, `enter`/`i` info, `l` logs, `s` stop,
@@ -631,7 +631,7 @@ export class PsTopComponent implements Component {
631
631
 
632
632
  #header(width: number, title: string): string {
633
633
  const age = this.#lastRefresh ? `updated ${formatDuration(Date.now() - this.#lastRefresh)} ago` : "updating…";
634
- const left = ` ${chalk.bold("omp ps")} ${chalk.dim("·")} ${title}`;
634
+ const left = ` ${chalk.bold("zetacode ps")} ${chalk.dim("·")} ${title}`;
635
635
  const right = chalk.dim(age);
636
636
  const pad = Math.max(1, width - Bun.stringWidth(left) - Bun.stringWidth(right) - 1);
637
637
  return truncateToWidth(`${left}${" ".repeat(pad)}${right}`, width);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Standalone TUI model picker used by `omp setup speech`.
2
+ * Standalone TUI model picker used by `zetacode setup speech`.
3
3
  *
4
4
  * One-shot {@link selectStandaloneItem} prompt: resolve on select/cancel and
5
5
  * let the adapter tear the UI down. The standalone TUI auto-renders on input,
@@ -77,8 +77,11 @@ export interface AutocompleteProvider {
77
77
  };
78
78
  /** Get inline hint text to show as dim ghost text after the cursor */
79
79
  getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
80
- /** Synchronously try to complete a slash command at the start of a line (no async I/O). */
81
- /** Returns matched items and the full prefix, or null if not applicable. */
80
+ /**
81
+ * Synchronously list slash command-name completions for a leading slash token (no async I/O).
82
+ * Mirrors the command-name branch of {@link getSuggestions}, including the bare `/` listing and
83
+ * the collapsed `/skill:` namespace row. Returns null outside a command-name token or with no match.
84
+ */
82
85
  trySyncSlashCompletion?(textBeforeCursor: string): {
83
86
  items: AutocompleteItem[];
84
87
  prefix: string;
@@ -263,8 +263,11 @@ export interface AutocompleteProvider {
263
263
 
264
264
  /** Get inline hint text to show as dim ghost text after the cursor */
265
265
  getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
266
- /** Synchronously try to complete a slash command at the start of a line (no async I/O). */
267
- /** Returns matched items and the full prefix, or null if not applicable. */
266
+ /**
267
+ * Synchronously list slash command-name completions for a leading slash token (no async I/O).
268
+ * Mirrors the command-name branch of {@link getSuggestions}, including the bare `/` listing and
269
+ * the collapsed `/skill:` namespace row. Returns null outside a command-name token or with no match.
270
+ */
268
271
  trySyncSlashCompletion?(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null;
269
272
  /**
270
273
  * Synchronously try to expand text immediately before the cursor (no async I/O).
@@ -589,6 +592,22 @@ function buildMidPromptSkillCompletions(commands: CommandEntry[], lowerPrefix: s
589
592
  );
590
593
  }
591
594
 
595
+ const DIR_CACHE_MAX = 100;
596
+ const DIR_CACHE_EVICT = 50;
597
+
598
+ /**
599
+ * Drops the `count` oldest entries once `map` exceeds `max`. Callers delete a
600
+ * key before re-setting it, so Map insertion order is refresh order.
601
+ */
602
+ function evictOldest(map: Map<string, unknown>, max: number, count: number): void {
603
+ if (map.size <= max) return;
604
+ let remaining = count;
605
+ for (const key of map.keys()) {
606
+ if (remaining-- === 0) break;
607
+ map.delete(key);
608
+ }
609
+ }
610
+
592
611
  // Combined provider that handles both slash commands and file paths.
593
612
  export class CombinedAutocompleteProvider implements AutocompleteProvider {
594
613
  #commands: CommandEntry[];
@@ -598,6 +617,9 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
598
617
  // per-directory readdir fast-path for prefix completions. Global fuzzy
599
618
  // discovery continues to use native fuzzyFind + shared scan cache.
600
619
  #dirCache: Map<string, { entries: fs.Dirent[]; timestamp: number }> = new Map();
620
+ // Whether a scoped fuzzy query's base (`src/` in `@src/foo`) is a directory;
621
+ // stat'ing it on every keystroke is redundant while the base is unchanged.
622
+ #scopedBaseCache: Map<string, { isDirectory: boolean; timestamp: number }> = new Map();
601
623
  readonly #DIR_CACHE_TTL = 2000; // 2 seconds
602
624
 
603
625
  constructor(
@@ -806,7 +828,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
806
828
  };
807
829
  }
808
830
 
809
- // Slash command suggestions can be accepted before the debounced refresh
831
+ // Slash command suggestions can be accepted before an async refresh
810
832
  // catches up to newly typed characters. Replace the live command token,
811
833
  // not only the prefix captured when the suggestion list was rendered.
812
834
  // Absolute-path completions share the leading-slash prefix shape but
@@ -976,11 +998,21 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
976
998
  baseDir = path.join(this.#basePath, displayBase);
977
999
  }
978
1000
 
979
- try {
980
- if (!(await fs.promises.stat(baseDir)).isDirectory()) {
981
- return null;
1001
+ const now = Date.now();
1002
+ let cachedBase = this.#scopedBaseCache.get(baseDir);
1003
+ if (!cachedBase || now - cachedBase.timestamp >= this.#DIR_CACHE_TTL) {
1004
+ let isDirectory = false;
1005
+ try {
1006
+ isDirectory = (await fs.promises.stat(baseDir)).isDirectory();
1007
+ } catch {
1008
+ // Missing or inaccessible base: not a scope.
982
1009
  }
983
- } catch {
1010
+ cachedBase = { isDirectory, timestamp: now };
1011
+ this.#scopedBaseCache.delete(baseDir);
1012
+ this.#scopedBaseCache.set(baseDir, cachedBase);
1013
+ evictOldest(this.#scopedBaseCache, DIR_CACHE_MAX, DIR_CACHE_EVICT);
1014
+ }
1015
+ if (!cachedBase.isDirectory) {
984
1016
  return null;
985
1017
  }
986
1018
 
@@ -1003,17 +1035,9 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
1003
1035
  }
1004
1036
 
1005
1037
  const entries = await fs.promises.readdir(searchDir, { withFileTypes: true });
1038
+ this.#dirCache.delete(searchDir);
1006
1039
  this.#dirCache.set(searchDir, { entries, timestamp: now });
1007
-
1008
- if (this.#dirCache.size > 100) {
1009
- const sortedKeys = [...this.#dirCache.entries()]
1010
- .sort((a, b) => a[1].timestamp - b[1].timestamp)
1011
- .slice(0, 50)
1012
- .map(([key]) => key);
1013
- for (const key of sortedKeys) {
1014
- this.#dirCache.delete(key);
1015
- }
1016
- }
1040
+ evictOldest(this.#dirCache, DIR_CACHE_MAX, DIR_CACHE_EVICT);
1017
1041
 
1018
1042
  return entries;
1019
1043
  }
@@ -1021,8 +1045,10 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
1021
1045
  invalidateDirCache(dir?: string): void {
1022
1046
  if (dir) {
1023
1047
  this.#dirCache.delete(dir);
1048
+ this.#scopedBaseCache.delete(dir);
1024
1049
  } else {
1025
1050
  this.#dirCache.clear();
1051
+ this.#scopedBaseCache.clear();
1026
1052
  }
1027
1053
  }
1028
1054
 
@@ -1087,26 +1113,29 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
1087
1113
  const entries = await this.#getCachedDirEntries(searchDir);
1088
1114
  const suggestions: AutocompleteItem[] = [];
1089
1115
 
1090
- for (const entry of entries) {
1091
- if (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) {
1092
- continue;
1093
- }
1094
- // Skip .git directory
1095
- if (entry.name === ".git") {
1096
- continue;
1097
- }
1116
+ const lowerSearchPrefix = searchPrefix.toLowerCase();
1117
+ // Skip .git directory
1118
+ const matched = entries.filter(
1119
+ entry => entry.name !== ".git" && entry.name.toLowerCase().startsWith(lowerSearchPrefix),
1120
+ );
1121
+ // Directory flag per match; symlinks are stat'ed concurrently to learn whether
1122
+ // they point at a directory. `null` drops a broken symlink, a file deleted
1123
+ // between readdir and stat, or a permission error.
1124
+ const directoryFlags = await Promise.all(
1125
+ matched.map(entry =>
1126
+ !entry.isDirectory() && entry.isSymbolicLink()
1127
+ ? fs.promises.stat(path.join(searchDir, entry.name)).then(
1128
+ stats => stats.isDirectory(),
1129
+ () => null,
1130
+ )
1131
+ : entry.isDirectory(),
1132
+ ),
1133
+ );
1098
1134
 
1099
- // Check if entry is a directory (or a symlink pointing to a directory)
1100
- let isDirectory = entry.isDirectory();
1101
- if (!isDirectory && entry.isSymbolicLink()) {
1102
- try {
1103
- const fullPath = path.join(searchDir, entry.name);
1104
- isDirectory = (await fs.promises.stat(fullPath)).isDirectory();
1105
- } catch {
1106
- // Broken symlink, file deleted between readdir and stat, or permission error
1107
- continue;
1108
- }
1109
- }
1135
+ for (let index = 0; index < matched.length; index++) {
1136
+ const isDirectory = directoryFlags[index];
1137
+ if (typeof isDirectory !== "boolean") continue;
1138
+ const entry = matched[index]!;
1110
1139
 
1111
1140
  let relativePath: string;
1112
1141
  const name = entry.name;
@@ -1300,19 +1329,14 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
1300
1329
  const slashStart = findLeadingSlashCommandStart(textBeforeCursor);
1301
1330
  if (slashStart === null) return null;
1302
1331
  const commandText = textBeforeCursor.slice(slashStart);
1303
- if (commandText.length <= 1) return null; // Bare "/" alone, don't auto-complete
1304
1332
  if (commandText.includes(" ")) return null; // Only complete command name, not args
1305
1333
 
1306
- const prefix = commandText.slice(1);
1307
- const lowerPrefix = prefix.toLowerCase();
1308
-
1309
- // The `/skill:` namespace row is excluded here: the sync path submits
1310
- // immediately after applying, and the bare namespace is not a command.
1334
+ const lowerPrefix = commandText.slice(1).toLowerCase();
1311
1335
  const matches = buildSlashCommandCompletions(
1312
1336
  collapseSkillNamespace(this.#commands, lowerPrefix),
1313
1337
  lowerPrefix,
1314
1338
  this.#commandUsage,
1315
- ).filter(item => item.value !== SKILL_NAMESPACE);
1339
+ );
1316
1340
 
1317
1341
  if (matches.length === 0) return null;
1318
1342
  // Mirror `getSuggestions`: preserve leading whitespace so the editor's