@oh-my-pi/pi-tui 18.4.2 → 18.4.4

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 (463) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/types/apps/cleanse-board.d.ts +17 -0
  3. package/dist/types/apps/git/help.d.ts +16 -0
  4. package/dist/types/apps/git/sidebar.d.ts +18 -0
  5. package/dist/types/apps/live-visualizer.d.ts +10 -0
  6. package/dist/types/apps/ps-top.d.ts +15 -0
  7. package/dist/types/apps/standalone-picker.d.ts +3 -0
  8. package/dist/types/autocomplete.d.ts +8 -0
  9. package/dist/types/chat/advisor-message.d.ts +12 -2
  10. package/dist/types/chat/assistant-message.d.ts +20 -0
  11. package/dist/types/chat/bash-execution.d.ts +9 -0
  12. package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
  13. package/dist/types/chat/collab-prompt-message.d.ts +4 -0
  14. package/dist/types/chat/compaction-summary-message.d.ts +15 -0
  15. package/dist/types/chat/eval-execution.d.ts +8 -0
  16. package/dist/types/chat/execution-shared.d.ts +58 -0
  17. package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
  18. package/dist/types/chat/read-tool-group.d.ts +15 -0
  19. package/dist/types/chat/skill-message.d.ts +9 -0
  20. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
  21. package/dist/types/chat/thinking-display.d.ts +2 -0
  22. package/dist/types/chat/todo-reminder.d.ts +4 -1
  23. package/dist/types/chat/tool-execution.d.ts +25 -1
  24. package/dist/types/chat/transcript-actions.d.ts +37 -0
  25. package/dist/types/chat/transcript-entry.d.ts +3 -1
  26. package/dist/types/chat/user-message.d.ts +25 -0
  27. package/dist/types/chrome/collab-qrcode.d.ts +7 -0
  28. package/dist/types/chrome/context-thresholds.d.ts +11 -0
  29. package/dist/types/chrome/countdown-timer.d.ts +9 -3
  30. package/dist/types/chrome/diff.d.ts +12 -0
  31. package/dist/types/chrome/dynamic-border.d.ts +3 -0
  32. package/dist/types/chrome/format.d.ts +6 -0
  33. package/dist/types/chrome/message-divider.d.ts +13 -0
  34. package/dist/types/chrome/message-frame.d.ts +10 -0
  35. package/dist/types/chrome/message-notice.d.ts +23 -0
  36. package/dist/types/chrome/overlay-box.d.ts +11 -2
  37. package/dist/types/chrome/qrcode.d.ts +7 -0
  38. package/dist/types/chrome/segment-track.d.ts +7 -0
  39. package/dist/types/chrome/selector-helpers.d.ts +0 -6
  40. package/dist/types/chrome/status-notice.d.ts +14 -0
  41. package/dist/types/chrome/streaming-panel.d.ts +3 -0
  42. package/dist/types/chrome/tool-activity.d.ts +3 -0
  43. package/dist/types/chrome/transcript-container.d.ts +13 -0
  44. package/dist/types/chrome/transcript-status.d.ts +15 -3
  45. package/dist/types/components/box.d.ts +7 -0
  46. package/dist/types/components/disclosure.d.ts +9 -0
  47. package/dist/types/components/editor.d.ts +42 -0
  48. package/dist/types/components/form.d.ts +14 -0
  49. package/dist/types/components/image.d.ts +7 -0
  50. package/dist/types/components/input.d.ts +20 -1
  51. package/dist/types/components/key-value-list.d.ts +3 -0
  52. package/dist/types/components/layout/geometry.d.ts +16 -0
  53. package/dist/types/components/layout/row.d.ts +7 -0
  54. package/dist/types/components/layout/split-pane.d.ts +8 -0
  55. package/dist/types/components/layout/stack.d.ts +6 -0
  56. package/dist/types/components/loader.d.ts +48 -0
  57. package/dist/types/components/markdown.d.ts +8 -0
  58. package/dist/types/components/metric.d.ts +7 -0
  59. package/dist/types/components/progress-bar.d.ts +3 -0
  60. package/dist/types/components/scroll-view.d.ts +6 -0
  61. package/dist/types/components/section.d.ts +3 -0
  62. package/dist/types/components/select-list.d.ts +33 -0
  63. package/dist/types/components/settings-list.d.ts +68 -0
  64. package/dist/types/components/spacer.d.ts +4 -0
  65. package/dist/types/components/tab-bar.d.ts +9 -0
  66. package/dist/types/components/table.d.ts +7 -0
  67. package/dist/types/components/text.d.ts +6 -0
  68. package/dist/types/components/tree-view.d.ts +9 -0
  69. package/dist/types/components/truncated-text.d.ts +3 -0
  70. package/dist/types/components/wizard-step.d.ts +6 -0
  71. package/dist/types/hotkeys-markdown.d.ts +24 -0
  72. package/dist/types/index.d.ts +5 -0
  73. package/dist/types/latex-block.d.ts +7 -0
  74. package/dist/types/loop-watchdog.d.ts +9 -0
  75. package/dist/types/native/apply.d.ts +39 -0
  76. package/dist/types/native/backend.d.ts +94 -0
  77. package/dist/types/native/blobs.d.ts +29 -0
  78. package/dist/types/native/describe.d.ts +56 -0
  79. package/dist/types/native/encode.d.ts +50 -0
  80. package/dist/types/native/icons.d.ts +18 -0
  81. package/dist/types/native/memo.d.ts +27 -0
  82. package/dist/types/native/node.d.ts +107 -0
  83. package/dist/types/native/overlay.d.ts +64 -0
  84. package/dist/types/native/picker.d.ts +124 -0
  85. package/dist/types/native/reconcile.d.ts +41 -0
  86. package/dist/types/native/settle.d.ts +11 -0
  87. package/dist/types/native/spans.d.ts +21 -0
  88. package/dist/types/native/state.d.ts +6 -0
  89. package/dist/types/native/tone.d.ts +24 -0
  90. package/dist/types/overlays/advisor-config.d.ts +20 -0
  91. package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
  92. package/dist/types/overlays/agent-hub.d.ts +10 -0
  93. package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
  94. package/dist/types/overlays/agents-hub.d.ts +8 -0
  95. package/dist/types/overlays/annotation-overlay.d.ts +3 -0
  96. package/dist/types/overlays/ask-dialog.d.ts +23 -0
  97. package/dist/types/overlays/bordered-loader.d.ts +3 -0
  98. package/dist/types/overlays/btw-history-panel.d.ts +9 -0
  99. package/dist/types/overlays/btw-panel.d.ts +3 -0
  100. package/dist/types/overlays/cleanse-panel.d.ts +5 -2
  101. package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
  102. package/dist/types/overlays/copy-selector.d.ts +38 -0
  103. package/dist/types/overlays/error-banner.d.ts +9 -0
  104. package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
  105. package/dist/types/overlays/history-search.d.ts +18 -0
  106. package/dist/types/overlays/hook-editor.d.ts +28 -1
  107. package/dist/types/overlays/hook-input.d.ts +3 -0
  108. package/dist/types/overlays/hook-selector.d.ts +10 -0
  109. package/dist/types/overlays/hub-frame.d.ts +18 -0
  110. package/dist/types/overlays/jobs-panel.d.ts +24 -0
  111. package/dist/types/overlays/login-dialog.d.ts +10 -0
  112. package/dist/types/overlays/logout-account-selector.d.ts +4 -0
  113. package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
  114. package/dist/types/overlays/model-browser.d.ts +69 -0
  115. package/dist/types/overlays/model-hub.d.ts +7 -0
  116. package/dist/types/overlays/model-picker.d.ts +9 -1
  117. package/dist/types/overlays/move-overlay.d.ts +3 -0
  118. package/dist/types/overlays/oauth-selector.d.ts +4 -0
  119. package/dist/types/overlays/omfg-panel.d.ts +3 -0
  120. package/dist/types/overlays/pause-screen.d.ts +12 -0
  121. package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
  122. package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
  123. package/dist/types/overlays/plugin-selector.d.ts +4 -0
  124. package/dist/types/overlays/plugin-settings.d.ts +30 -0
  125. package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
  126. package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
  127. package/dist/types/overlays/rewind-selector.d.ts +5 -0
  128. package/dist/types/overlays/session-info-overlay.d.ts +19 -1
  129. package/dist/types/overlays/session-selector.d.ts +64 -1
  130. package/dist/types/overlays/settings-defs.d.ts +14 -0
  131. package/dist/types/overlays/settings-selector.d.ts +21 -0
  132. package/dist/types/overlays/show-images-selector.d.ts +4 -0
  133. package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
  134. package/dist/types/overlays/stats-notice.d.ts +8 -0
  135. package/dist/types/overlays/theme-selector.d.ts +11 -0
  136. package/dist/types/overlays/thinking-selector.d.ts +4 -0
  137. package/dist/types/overlays/tree-selector.d.ts +60 -2
  138. package/dist/types/overlays/usage-dashboard.d.ts +26 -3
  139. package/dist/types/overlays/usage-row.d.ts +36 -2
  140. package/dist/types/prompt/attachment-chips.d.ts +8 -0
  141. package/dist/types/prompt/composer-attachments.d.ts +15 -3
  142. package/dist/types/prompt/composer.d.ts +18 -1
  143. package/dist/types/prompt/custom-editor.d.ts +48 -2
  144. package/dist/types/prompt/editor-top-gap.d.ts +2 -0
  145. package/dist/types/prompt/macos-spelling.d.ts +6 -0
  146. package/dist/types/prompt/magic-keywords.d.ts +9 -0
  147. package/dist/types/prompt/queued-messages.d.ts +23 -0
  148. package/dist/types/prompt/skill-tokens.d.ts +4 -2
  149. package/dist/types/prompt/welcome.d.ts +16 -0
  150. package/dist/types/render/code-cell.d.ts +10 -0
  151. package/dist/types/render/file-list.d.ts +8 -0
  152. package/dist/types/render/hyperlink.d.ts +14 -0
  153. package/dist/types/render/output-block.d.ts +43 -0
  154. package/dist/types/render/output-pane.d.ts +17 -0
  155. package/dist/types/render/tool-card.d.ts +8 -0
  156. package/dist/types/render/tree-list.d.ts +8 -0
  157. package/dist/types/render/width-aware-text.d.ts +7 -0
  158. package/dist/types/setup/scenes/outro.d.ts +7 -0
  159. package/dist/types/setup/scenes/sign-in.d.ts +16 -9
  160. package/dist/types/setup/scenes/splash.d.ts +8 -0
  161. package/dist/types/setup/scenes/types.d.ts +6 -27
  162. package/dist/types/setup/wizard-overlay.d.ts +9 -0
  163. package/dist/types/status-line/component.d.ts +49 -5
  164. package/dist/types/status-line/context-usage.d.ts +20 -0
  165. package/dist/types/status-line/footer.d.ts +8 -0
  166. package/dist/types/status-line/metrics.d.ts +1 -2
  167. package/dist/types/status-line/segments.d.ts +10 -1
  168. package/dist/types/status-line/types.d.ts +40 -1
  169. package/dist/types/stdin-buffer.d.ts +10 -7
  170. package/dist/types/terminal-capabilities.d.ts +1 -1
  171. package/dist/types/terminal.d.ts +36 -0
  172. package/dist/types/theme/defaults/index.d.ts +100 -0
  173. package/dist/types/theme/schema.d.ts +2 -0
  174. package/dist/types/theme/shimmer.d.ts +8 -0
  175. package/dist/types/theme/theme-class.d.ts +2 -0
  176. package/dist/types/theme/theme.d.ts +33 -1
  177. package/dist/types/tools/ask.d.ts +3 -0
  178. package/dist/types/tools/ast-edit.d.ts +10 -1
  179. package/dist/types/tools/ast-grep.d.ts +10 -1
  180. package/dist/types/tools/autoresearch.d.ts +21 -4
  181. package/dist/types/tools/bash.d.ts +30 -1
  182. package/dist/types/tools/cfg-render.d.ts +5 -1
  183. package/dist/types/tools/debug.d.ts +3 -1
  184. package/dist/types/tools/default-renderer.d.ts +6 -1
  185. package/dist/types/tools/edit.d.ts +14 -1
  186. package/dist/types/tools/eval.d.ts +12 -1
  187. package/dist/types/tools/fetch.d.ts +16 -1
  188. package/dist/types/tools/find.d.ts +15 -1
  189. package/dist/types/tools/github.d.ts +3 -0
  190. package/dist/types/tools/glob.d.ts +14 -1
  191. package/dist/types/tools/goal.d.ts +3 -1
  192. package/dist/types/tools/grep.d.ts +14 -1
  193. package/dist/types/tools/grouped-file-output.d.ts +58 -0
  194. package/dist/types/tools/json-tree.d.ts +20 -0
  195. package/dist/types/tools/lsp.d.ts +3 -10
  196. package/dist/types/tools/mcp.d.ts +16 -1
  197. package/dist/types/tools/memory.d.ts +26 -0
  198. package/dist/types/tools/native-view.d.ts +90 -0
  199. package/dist/types/tools/proc-render.d.ts +7 -1
  200. package/dist/types/tools/read.d.ts +23 -1
  201. package/dist/types/tools/renderer.d.ts +57 -0
  202. package/dist/types/tools/report-tool-issue.d.ts +3 -0
  203. package/dist/types/tools/resolve.d.ts +14 -1
  204. package/dist/types/tools/task.d.ts +3 -0
  205. package/dist/types/tools/think.d.ts +3 -0
  206. package/dist/types/tools/todo.d.ts +12 -0
  207. package/dist/types/tools/vibe.d.ts +3 -1
  208. package/dist/types/tools/wait.d.ts +3 -0
  209. package/dist/types/tools/web-search-types.d.ts +90 -0
  210. package/dist/types/tools/web-search.d.ts +9 -168
  211. package/dist/types/tools/write.d.ts +14 -1
  212. package/dist/types/tools/xdev.d.ts +13 -0
  213. package/dist/types/tui.d.ts +49 -0
  214. package/package.json +9 -9
  215. package/src/apps/autoresearch-dashboard.ts +362 -4
  216. package/src/apps/cleanse-board.ts +219 -1
  217. package/src/apps/cleanse-picker.ts +9 -1
  218. package/src/apps/debug/log-viewer.ts +15 -15
  219. package/src/apps/debug/protocol-probe.ts +2 -20
  220. package/src/apps/git/avatar.ts +2 -22
  221. package/src/apps/git/git-tui.ts +406 -59
  222. package/src/apps/git/help.ts +130 -0
  223. package/src/apps/git/sidebar.ts +491 -0
  224. package/src/apps/live-visualizer.ts +65 -0
  225. package/src/apps/ps-top.ts +448 -17
  226. package/src/apps/session-picker.ts +1 -0
  227. package/src/apps/setup-model-picker.ts +5 -1
  228. package/src/apps/standalone-picker.ts +55 -1
  229. package/src/autocomplete.ts +44 -1
  230. package/src/chat/advisor-message.ts +72 -3
  231. package/src/chat/assistant-message.ts +538 -51
  232. package/src/chat/background-tan-message.ts +4 -4
  233. package/src/chat/bash-execution.ts +81 -0
  234. package/src/chat/cache-invalidation-marker.ts +8 -0
  235. package/src/chat/chat-transcript-builder.ts +37 -6
  236. package/src/chat/collab-prompt-message.ts +15 -3
  237. package/src/chat/compaction-summary-message.ts +74 -1
  238. package/src/chat/custom-message.ts +3 -1
  239. package/src/chat/eval-execution.ts +57 -0
  240. package/src/chat/execution-shared.ts +153 -1
  241. package/src/chat/hook-message.ts +1 -0
  242. package/src/chat/late-diagnostics-message.ts +85 -2
  243. package/src/chat/read-tool-group.ts +341 -1
  244. package/src/chat/served-model-marker.ts +8 -0
  245. package/src/chat/skill-message.ts +63 -0
  246. package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
  247. package/src/chat/thinking-display.ts +73 -11
  248. package/src/chat/todo-reminder.ts +34 -4
  249. package/src/chat/tool-execution.ts +385 -10
  250. package/src/chat/transcript-actions.ts +38 -0
  251. package/src/chat/transcript-browser.ts +2 -9
  252. package/src/chat/transcript-entry.ts +10 -1
  253. package/src/chat/transcript-render-helpers.ts +16 -15
  254. package/src/chat/ttsr-notification.ts +38 -1
  255. package/src/chat/user-message.ts +167 -4
  256. package/src/chrome/collab-qrcode.ts +27 -2
  257. package/src/chrome/context-thresholds.ts +34 -0
  258. package/src/chrome/countdown-timer.ts +24 -1
  259. package/src/chrome/diff.ts +60 -1
  260. package/src/chrome/dynamic-border.ts +7 -0
  261. package/src/chrome/format.ts +12 -0
  262. package/src/chrome/message-divider.ts +33 -1
  263. package/src/chrome/message-frame.ts +53 -10
  264. package/src/chrome/message-notice.ts +71 -0
  265. package/src/chrome/overlay-box.ts +45 -2
  266. package/src/chrome/qrcode.ts +28 -0
  267. package/src/chrome/segment-track.ts +26 -0
  268. package/src/chrome/selector-helpers.ts +1 -17
  269. package/src/chrome/status-notice.ts +46 -0
  270. package/src/chrome/streaming-panel.ts +25 -1
  271. package/src/chrome/tool-activity.ts +20 -0
  272. package/src/chrome/transcript-container.ts +139 -17
  273. package/src/chrome/transcript-status.ts +40 -11
  274. package/src/components/box.ts +51 -0
  275. package/src/components/disclosure.ts +33 -0
  276. package/src/components/editor.ts +239 -36
  277. package/src/components/form.ts +93 -9
  278. package/src/components/image.ts +30 -0
  279. package/src/components/input.ts +77 -26
  280. package/src/components/key-value-list.ts +18 -0
  281. package/src/components/layout/geometry.ts +44 -0
  282. package/src/components/layout/row.ts +30 -0
  283. package/src/components/layout/split-pane.ts +65 -0
  284. package/src/components/layout/stack.ts +27 -0
  285. package/src/components/loader.ts +183 -1
  286. package/src/components/markdown.ts +131 -56
  287. package/src/components/metric.ts +39 -0
  288. package/src/components/progress-bar.ts +28 -0
  289. package/src/components/scroll-view.ts +23 -0
  290. package/src/components/section.ts +16 -0
  291. package/src/components/select-list.ts +174 -25
  292. package/src/components/settings-list.ts +264 -7
  293. package/src/components/spacer.ts +17 -0
  294. package/src/components/tab-bar.ts +42 -0
  295. package/src/components/table.ts +34 -0
  296. package/src/components/text.ts +23 -0
  297. package/src/components/tree-view.ts +65 -0
  298. package/src/components/truncated-text.ts +14 -0
  299. package/src/components/wizard-step.ts +18 -9
  300. package/src/debug-server.ts +14 -7
  301. package/src/hotkeys-markdown.ts +251 -69
  302. package/src/index.ts +6 -0
  303. package/src/latex-block.ts +11 -0
  304. package/src/loop-watchdog.ts +38 -10
  305. package/src/native/apply.ts +275 -0
  306. package/src/native/backend.ts +682 -0
  307. package/src/native/blobs.ts +70 -0
  308. package/src/native/describe.ts +124 -0
  309. package/src/native/encode.ts +233 -0
  310. package/src/native/icons.ts +184 -0
  311. package/src/native/memo.ts +64 -0
  312. package/src/native/node.ts +112 -0
  313. package/src/native/overlay.ts +131 -0
  314. package/src/native/picker.ts +333 -0
  315. package/src/native/reconcile.ts +1063 -0
  316. package/src/native/settle.ts +23 -0
  317. package/src/native/spans.ts +308 -0
  318. package/src/native/state.ts +28 -0
  319. package/src/native/tone.ts +79 -0
  320. package/src/overlays/advisor-config.ts +420 -0
  321. package/src/overlays/agent-hub-renderer.ts +155 -18
  322. package/src/overlays/agent-hub.ts +1037 -44
  323. package/src/overlays/agent-transcript-viewer.ts +129 -0
  324. package/src/overlays/agents-hub.ts +688 -53
  325. package/src/overlays/annotation-overlay.ts +391 -52
  326. package/src/overlays/ask-dialog.ts +435 -24
  327. package/src/overlays/bordered-loader.ts +17 -0
  328. package/src/overlays/btw-history-panel.ts +182 -0
  329. package/src/overlays/btw-panel.ts +68 -2
  330. package/src/overlays/cleanse-panel.ts +105 -15
  331. package/src/overlays/codex-reset-fireworks.ts +65 -5
  332. package/src/overlays/composer-shape-preview.ts +26 -1
  333. package/src/overlays/copy-selector.ts +545 -23
  334. package/src/overlays/error-banner.ts +44 -0
  335. package/src/overlays/extensions/extension-list.ts +23 -17
  336. package/src/overlays/history-search.ts +175 -33
  337. package/src/overlays/hook-editor.ts +113 -6
  338. package/src/overlays/hook-input.ts +22 -1
  339. package/src/overlays/hook-selector.ts +311 -22
  340. package/src/overlays/hub-frame.ts +80 -0
  341. package/src/overlays/jobs-panel.ts +112 -0
  342. package/src/overlays/login-dialog.ts +183 -1
  343. package/src/overlays/logout-account-selector.ts +102 -4
  344. package/src/overlays/mcp-add-wizard.ts +232 -53
  345. package/src/overlays/model-browser.ts +681 -22
  346. package/src/overlays/model-hub.ts +1402 -78
  347. package/src/overlays/model-picker.ts +184 -7
  348. package/src/overlays/move-overlay.ts +45 -0
  349. package/src/overlays/oauth-selector.ts +227 -23
  350. package/src/overlays/omfg-panel.ts +86 -2
  351. package/src/overlays/pause-screen.ts +55 -4
  352. package/src/overlays/plan-review-overlay.ts +352 -0
  353. package/src/overlays/plan-save-overlay.ts +45 -0
  354. package/src/overlays/plugin-selector.ts +21 -1
  355. package/src/overlays/plugin-settings.ts +195 -5
  356. package/src/overlays/queue-mode-selector.ts +19 -1
  357. package/src/overlays/reset-usage-selector.ts +110 -40
  358. package/src/overlays/rewind-selector.ts +458 -44
  359. package/src/overlays/session-account-selector.ts +1 -1
  360. package/src/overlays/session-info-overlay.ts +137 -1
  361. package/src/overlays/session-selector.ts +807 -46
  362. package/src/overlays/settings-defs.ts +41 -0
  363. package/src/overlays/settings-selector.ts +585 -33
  364. package/src/overlays/show-images-selector.ts +18 -1
  365. package/src/overlays/snapcompact-shape-preview.ts +48 -3
  366. package/src/overlays/stats-notice.ts +31 -0
  367. package/src/overlays/theme-selector.ts +31 -1
  368. package/src/overlays/thinking-selector.ts +22 -1
  369. package/src/overlays/tree-selector.ts +570 -110
  370. package/src/overlays/usage-dashboard.ts +587 -12
  371. package/src/overlays/usage-row.ts +161 -25
  372. package/src/prompt/attachment-chips.ts +60 -0
  373. package/src/prompt/composer-attachments.ts +31 -9
  374. package/src/prompt/composer.ts +55 -2
  375. package/src/prompt/custom-editor.ts +302 -4
  376. package/src/prompt/editor-top-gap.ts +8 -0
  377. package/src/prompt/macos-spelling.ts +24 -9
  378. package/src/prompt/magic-keywords.ts +22 -1
  379. package/src/prompt/model-mention-autocomplete.ts +1 -0
  380. package/src/prompt/queued-messages.ts +101 -0
  381. package/src/prompt/skill-tokens.ts +5 -3
  382. package/src/prompt/welcome.ts +213 -6
  383. package/src/render/code-cell.ts +103 -2
  384. package/src/render/file-list.ts +29 -0
  385. package/src/render/hyperlink.ts +32 -0
  386. package/src/render/output-block.ts +110 -0
  387. package/src/render/output-pane.ts +37 -0
  388. package/src/render/tool-card.ts +137 -0
  389. package/src/render/tree-list.ts +39 -0
  390. package/src/render/width-aware-text.ts +20 -0
  391. package/src/setup/scenes/composer.ts +29 -0
  392. package/src/setup/scenes/glyph.ts +15 -0
  393. package/src/setup/scenes/model.ts +36 -8
  394. package/src/setup/scenes/outro.ts +28 -1
  395. package/src/setup/scenes/sign-in.ts +138 -24
  396. package/src/setup/scenes/splash.ts +28 -1
  397. package/src/setup/scenes/theme.ts +88 -3
  398. package/src/setup/scenes/types.ts +7 -28
  399. package/src/setup/startup-splash.ts +46 -9
  400. package/src/setup/wizard-overlay.ts +140 -23
  401. package/src/setup/wizard.ts +1 -1
  402. package/src/status-line/component.ts +414 -18
  403. package/src/status-line/context-usage.ts +279 -50
  404. package/src/status-line/footer.ts +94 -1
  405. package/src/status-line/metrics.ts +3 -6
  406. package/src/status-line/segments.ts +463 -97
  407. package/src/status-line/separators.ts +3 -16
  408. package/src/status-line/types.ts +43 -1
  409. package/src/stdin-buffer.ts +27 -21
  410. package/src/terminal-capabilities.ts +7 -0
  411. package/src/terminal.ts +153 -11
  412. package/src/theme/defaults/dark-neon-noir.json +100 -0
  413. package/src/theme/defaults/index.ts +2 -0
  414. package/src/theme/defaults/light-prism.json +5 -5
  415. package/src/theme/defaults/onyx.json +62 -62
  416. package/src/theme/mermaid-cache.ts +10 -1
  417. package/src/theme/schema.ts +15 -0
  418. package/src/theme/shimmer.ts +33 -0
  419. package/src/theme/theme-class.ts +24 -19
  420. package/src/theme/theme.ts +111 -9
  421. package/src/theme/tui-adapters.ts +14 -1
  422. package/src/tools/ask.ts +157 -1
  423. package/src/tools/ast-edit.ts +174 -1
  424. package/src/tools/ast-grep.ts +82 -2
  425. package/src/tools/autoresearch.ts +149 -1
  426. package/src/tools/bash.ts +233 -62
  427. package/src/tools/cfg-render.ts +66 -1
  428. package/src/tools/debug.ts +78 -1
  429. package/src/tools/default-renderer.ts +35 -1
  430. package/src/tools/edit.ts +357 -31
  431. package/src/tools/eval.ts +257 -1
  432. package/src/tools/fetch.ts +102 -3
  433. package/src/tools/find.ts +127 -1
  434. package/src/tools/github.ts +119 -10
  435. package/src/tools/glob.ts +116 -1
  436. package/src/tools/goal.ts +56 -1
  437. package/src/tools/grep.ts +83 -2
  438. package/src/tools/grouped-file-output.ts +205 -0
  439. package/src/tools/json-tree.ts +116 -1
  440. package/src/tools/lsp.ts +318 -1
  441. package/src/tools/mcp.ts +80 -1
  442. package/src/tools/memory.ts +93 -11
  443. package/src/tools/native-view.ts +292 -0
  444. package/src/tools/proc-render.ts +221 -1
  445. package/src/tools/read.ts +199 -4
  446. package/src/tools/renderer.ts +61 -0
  447. package/src/tools/report-tool-issue.ts +7 -1
  448. package/src/tools/resolve.ts +63 -1
  449. package/src/tools/streaming-output.ts +79 -16
  450. package/src/tools/task.ts +406 -4
  451. package/src/tools/think.ts +19 -1
  452. package/src/tools/todo.ts +89 -0
  453. package/src/tools/vibe.ts +230 -2
  454. package/src/tools/wait.ts +181 -1
  455. package/src/tools/web-search-types.ts +99 -0
  456. package/src/tools/web-search.ts +111 -170
  457. package/src/tools/write.ts +144 -5
  458. package/src/tools/xdev.ts +64 -1
  459. package/src/tui.ts +295 -4
  460. package/dist/types/setup/scenes/providers.d.ts +0 -3
  461. package/dist/types/setup/scenes/web-search.d.ts +0 -22
  462. package/src/setup/scenes/providers.ts +0 -110
  463. package/src/setup/scenes/web-search.ts +0 -165
@@ -1,13 +1,99 @@
1
+ import type { TspProps } from "@oh-my-pi/pi-wire";
2
+ import { elapsed, kbd, keyed, node, row, span, text } from "../native/describe";
3
+ import { plainText } from "../native/spans";
4
+ import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
5
+ import { isNativeRendering } from "../native/state";
6
+ import { describeShimmer, type ShimmerPalette, shimmerEnabled } from "../theme/shimmer";
1
7
  import type { TUI } from "../tui";
2
8
  import { getPaddingX, padding, sliceByColumn, visibleWidth } from "../utils";
3
9
  import { Text } from "./text";
4
10
 
11
+ const DEFAULT_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
5
12
  const RENDER_INTERVAL_MS = 1000 / 30;
6
13
  /** Milliseconds between spinner-frame advances; exported so time-derived spinners elsewhere tick at the Loader cadence. */
7
14
  export const SPINNER_ADVANCE_MS = 80;
8
15
  const RENDER_BACKPRESSURE_MULTIPLIER = 9;
9
16
  const MAX_BACKPRESSURE_FRAME_COST_MS = 200;
10
17
 
18
+ /** Native repaint cadence of a retry countdown ring (the terminal eases the ring between frames). */
19
+ const RETRY_METER_REPAINT_MS = 500;
20
+
21
+ /** A working-row variant (§6.5): a retry countdown, or context compaction. */
22
+ export type WorkingRowVariant =
23
+ | {
24
+ readonly kind: "retry";
25
+ readonly attempt: number;
26
+ readonly max: number;
27
+ /** The wait before the attempt, in ms, counted from the row's `startedAt`. */
28
+ readonly delayMs: number;
29
+ }
30
+ | { readonly kind: "compaction" };
31
+
32
+ /** What the dock's working row (§8.1, role `omp.working`) shows. */
33
+ export interface WorkingRowSpec {
34
+ /** The intent, or the variant's label ("Retrying · attempt 1 of 3", "Compacting context…"). */
35
+ readonly label: string;
36
+ /** Epoch ms the work started: the `elapsed` origin and the retry countdown start. */
37
+ readonly startedAt: number;
38
+ readonly variant?: WorkingRowVariant;
39
+ /** Shimmer palette of the label (a session-accented intent). */
40
+ readonly palette?: ShimmerPalette;
41
+ /** Live tok/s, docked before the stop control. */
42
+ readonly rate?: number;
43
+ /** Key id that interrupts (`escape`); undefined hides the stop control (Esc would not cancel). */
44
+ readonly interruptKey?: string;
45
+ }
46
+
47
+ /** A key id as tooltip keys: `escape` reads `esc`, the rest as bound. */
48
+ function titleKey(key: string): string {
49
+ return key === "escape" ? "esc" : key;
50
+ }
51
+
52
+ /**
53
+ * The dock's working row: `starburst` spinner (or a retry countdown ring),
54
+ * the intent shimmering, `·`, the elapsed time, a grow spacer, then the stop
55
+ * control whose click sends `interrupt`. Compaction adds an indeterminate
56
+ * `progress` before the stop control; a retry says "Cancel".
57
+ */
58
+ export function describeWorkingRow(spec: WorkingRowSpec, cx: DescribeContext, now = Date.now()): NativeNode {
59
+ const variant = spec.variant;
60
+ const children: NativeChild[] = [];
61
+ if (variant?.kind === "retry" && cx.supports("meter")) {
62
+ const remaining = Math.max(0, variant.delayMs - (now - spec.startedAt));
63
+ const value = variant.delayMs > 0 ? Math.round((remaining / variant.delayMs) * 100) / 100 : 0;
64
+ children.push(node("meter", { value, style: "ring", size: "sm" }, undefined, "countdown"));
65
+ } else {
66
+ children.push(node("spinner", { style: "starburst", tone: "accent" }, undefined, "spinner"));
67
+ }
68
+ const label = describeShimmer([{ text: spec.label, palette: spec.palette }], "label");
69
+ children.push(
70
+ node(label.k, { ...label.p, role: "omp.working.label" } as TspProps, label.c, label.key),
71
+ node("text", { text: "·", role: "omp.working.sep" }, undefined, "sep"),
72
+ keyed(elapsed(now - spec.startedAt), "elapsed"),
73
+ node("row", { grow: 1 }, undefined, "fill"),
74
+ );
75
+ if (variant?.kind === "compaction") children.push(node("progress", { value: null }, undefined, "progress"));
76
+ if (spec.rate !== undefined) children.push(node("rate", { value: spec.rate, unit: "tok/s" }, undefined, "rate"));
77
+ if (spec.interruptKey !== undefined) {
78
+ const verb = variant?.kind === "retry" ? "Cancel" : "Stop";
79
+ children.push(
80
+ node(
81
+ "row",
82
+ {
83
+ role: "omp.working.stop",
84
+ gap: "xs",
85
+ align: "center",
86
+ title: `${verb} ${titleKey(spec.interruptKey)}`,
87
+ actions: { click: "interrupt" },
88
+ },
89
+ [kbd(titleKey(spec.interruptKey)), text(verb)],
90
+ "stop",
91
+ ),
92
+ );
93
+ }
94
+ return row(children, { role: "omp.working", align: "center", gap: "sm" });
95
+ }
96
+
11
97
  type ColorFn = (str: string) => string;
12
98
 
13
99
  /**
@@ -20,7 +106,7 @@ export type LoaderMessageColorFn = ColorFn & {
20
106
 
21
107
  /** Animates a spinner and colorized message while asynchronous work is pending. */
22
108
  export class Loader extends Text {
23
- #frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
109
+ #frames = DEFAULT_SPINNER_FRAMES;
24
110
  #currentFrame = 0;
25
111
  #intervalId?: NodeJS.Timeout;
26
112
  #ui: TUI | null = null;
@@ -37,6 +123,10 @@ export class Loader extends Text {
37
123
  #layoutFrames: readonly string[];
38
124
  #layoutFrame: string;
39
125
  #trailer?: () => string | undefined;
126
+ #native?: { message: string; trailer: string; shimmer: boolean; node: NativeNode };
127
+ #working?: { spec: () => WorkingRowSpec; interrupt: () => void };
128
+ #workingNative?: { key: string; node: NativeNode };
129
+ #nativeTimer?: NodeJS.Timeout;
40
130
 
41
131
  constructor(
42
132
  ui: TUI,
@@ -136,10 +226,93 @@ export class Loader extends Text {
136
226
  return lines;
137
227
  }
138
228
 
229
+ /**
230
+ * A `spinner` (or the static glyph of a one-frame loader, e.g. an interrupt
231
+ * key hint) followed by the message, shimmering when the message colorizer
232
+ * animates, and the right-docked trailer. The terminal clocks the motion.
233
+ * Function messages are re-read whenever the engine revisits the loader.
234
+ */
235
+ override describe(cx: DescribeContext): NativeNode {
236
+ if (this.#working) return this.#describeWorking(cx);
237
+ const message = plainText(this.#resolveMessage());
238
+ const trailer = plainText(this.#trailer?.() ?? "");
239
+ const shimmer = this.messageColorFn.animated === true && shimmerEnabled();
240
+ const cached = this.#native;
241
+ if (cached && cached.message === message && cached.trailer === trailer && cached.shimmer === shimmer) {
242
+ return cached.node;
243
+ }
244
+ const indicator =
245
+ this.#frames.length > 1
246
+ ? node("spinner", this.#frames === DEFAULT_SPINNER_FRAMES ? { style: "braille" } : {})
247
+ : text([span(plainText(this.#frames[0] ?? "").trim(), "muted")], { wrap: "none" });
248
+ const label = shimmer
249
+ ? describeShimmer([{ text: message }], "message")
250
+ : node("text", { spans: [span(message, "muted")] }, undefined, "message");
251
+ const children: NativeChild[] = [row([indicator, label], { gap: "sm", align: "baseline" })];
252
+ if (trailer) children.push(text(trailer, { wrap: "none", truncate: "end" }));
253
+ const described = row(children, { justify: "between", role: "omp.loader" });
254
+ this.#native = { message, trailer, shimmer, node: described };
255
+ return described;
256
+ }
257
+
258
+ /**
259
+ * Describe as the dock's working row ({@link describeWorkingRow}) instead
260
+ * of a spinner line; `spec` is re-read whenever the engine revisits the
261
+ * loader and `interrupt` answers a click on its stop control.
262
+ */
263
+ setWorkingRow(spec: () => WorkingRowSpec, interrupt: () => void): void {
264
+ this.#working = { spec, interrupt };
265
+ this.#workingNative = undefined;
266
+ this.#startNativeCountdown();
267
+ }
268
+
269
+ /** A click on the working row's stop control. */
270
+ handleNativeEvent(event: NativeUiEvent): void {
271
+ if (event.type === "action" && event.act === "interrupt") this.#working?.interrupt();
272
+ }
273
+
274
+ #describeWorking(cx: DescribeContext): NativeNode {
275
+ const spec = this.#working!.spec();
276
+ const now = Date.now();
277
+ const variant = spec.variant;
278
+ const countdown =
279
+ variant?.kind === "retry" && cx.supports("meter")
280
+ ? Math.round((Math.max(0, variant.delayMs - (now - spec.startedAt)) / Math.max(1, variant.delayMs)) * 100)
281
+ : -1;
282
+ const key = [
283
+ spec.label,
284
+ spec.startedAt,
285
+ variant?.kind,
286
+ variant?.kind === "retry" ? `${variant.attempt}/${variant.max}` : "",
287
+ countdown,
288
+ spec.rate,
289
+ spec.interruptKey,
290
+ spec.palette?.mid,
291
+ shimmerEnabled(),
292
+ ].join("\u0000");
293
+ const cached = this.#workingNative;
294
+ if (cached?.key === key) return cached.node;
295
+ const described = describeWorkingRow(spec, cx, now);
296
+ this.#workingNative = { key, node: described };
297
+ return described;
298
+ }
299
+
300
+ /** A TSP terminal clocks the spinner and timer, but a retry ring's fill is omp's: repaint it while counting down. */
301
+ #startNativeCountdown(): void {
302
+ if (this.#nativeTimer || !isNativeRendering() || this.#working?.spec().variant?.kind !== "retry") return;
303
+ this.#nativeTimer = setInterval(() => this.#requestPaint(), RETRY_METER_REPAINT_MS);
304
+ this.#nativeTimer.unref?.();
305
+ }
306
+
139
307
  start() {
140
308
  this.#lastSpinnerTick = performance.now();
141
309
  this.#syncText();
142
310
  this.#requestPaint();
311
+ // The TSP terminal animates the described spinner; nothing to repaint.
312
+ if (isNativeRendering()) {
313
+ this.#startNativeCountdown();
314
+ return;
315
+ }
143
316
  const intervalMs = this.messageColorFn.animated === true ? RENDER_INTERVAL_MS : SPINNER_ADVANCE_MS;
144
317
  this.#scheduleTick(intervalMs, intervalMs);
145
318
  }
@@ -149,6 +322,10 @@ export class Loader extends Text {
149
322
  clearTimeout(this.#intervalId);
150
323
  this.#intervalId = undefined;
151
324
  }
325
+ if (this.#nativeTimer) {
326
+ clearInterval(this.#nativeTimer);
327
+ this.#nativeTimer = undefined;
328
+ }
152
329
  }
153
330
 
154
331
  /** Lifecycle teardown: stop the animation timer. Idempotent. */
@@ -174,6 +351,11 @@ export class Loader extends Text {
174
351
  #scheduleTick(intervalMs: number, delayMs: number): void {
175
352
  const timer = setTimeout(() => {
176
353
  if (this.#intervalId !== timer) return;
354
+ if (isNativeRendering()) {
355
+ // A TSP surface opened mid-animation: the terminal clocks it from here.
356
+ this.#intervalId = undefined;
357
+ return;
358
+ }
177
359
  const startedAt = performance.now();
178
360
  const elapsed = startedAt - this.#lastSpinnerTick;
179
361
  const shouldAdvanceSpinner = elapsed >= SPINNER_ADVANCE_MS;
@@ -10,6 +10,9 @@ import {
10
10
  import { mathBlockAt, mathSpanAt, mathStartIndex } from "@oh-my-pi/pi-utils/math-delimiters";
11
11
  import { latexToBlock } from "../latex-block";
12
12
  import { isBareMathEnvironment, latexToUnicode } from "../latex-to-unicode";
13
+ import { plainText } from "../native/spans";
14
+ import { md } from "../native/describe";
15
+ import type { DescribeContext, NativeNode } from "../native/node";
13
16
  import type { SymbolTheme } from "../symbols";
14
17
  import { TERMINAL } from "../terminal-capabilities";
15
18
  import type { Component } from "../tui";
@@ -880,7 +883,10 @@ for (const table of [Lexer.rules.block.normal, Lexer.rules.block.gfm]) {
880
883
 
881
884
  const RENDER_CACHE_MAX = 256; // sane cap: ~256 distinct message × width combos
882
885
  const RENDER_CACHE_MAX_SIZE = 4 * 1024 * 1024;
883
- const RENDER_CACHE_MAX_ENTRY_SIZE = 256 * 1024;
886
+ // Entry size counts the key (it embeds the whole normalized source) plus the
887
+ // rendered rows, so this admits roughly the same documents a rows-only 256 KiB
888
+ // cap did while the aggregate bound covers everything the entry retains.
889
+ const RENDER_CACHE_MAX_ENTRY_SIZE = 512 * 1024;
884
890
  const EMPTY_RENDER_LINES: readonly string[] = [];
885
891
 
886
892
  interface RenderedLine {
@@ -900,7 +906,7 @@ const renderCache = new LRUCache<string, readonly string[]>({
900
906
  max: RENDER_CACHE_MAX,
901
907
  maxSize: RENDER_CACHE_MAX_SIZE,
902
908
  maxEntrySize: RENDER_CACHE_MAX_ENTRY_SIZE,
903
- sizeCalculation: renderedLinesCacheSize,
909
+ sizeCalculation: (lines, key) => renderedLinesCacheSize(lines) + key.length,
904
910
  });
905
911
 
906
912
  function renderedLinesCacheSize(lines: readonly string[]): number {
@@ -909,6 +915,15 @@ function renderedLinesCacheSize(lines: readonly string[]): number {
909
915
  return Math.max(1, size);
910
916
  }
911
917
 
918
+ /**
919
+ * Append `src` onto `dst` element-wise. `dst.push(...src)` passes every row as
920
+ * a call argument, which throws past the engine's argument limit on very long
921
+ * documents.
922
+ */
923
+ function appendLines(dst: string[], src: readonly string[]): void {
924
+ for (let i = 0; i < src.length; i++) dst.push(src[i]!);
925
+ }
926
+
912
927
  // ---------------------------------------------------------------------------
913
928
  // Fast-tail (B+) hazard gates
914
929
  // ---------------------------------------------------------------------------
@@ -1661,7 +1676,9 @@ interface RenderSignature {
1661
1676
  interface StreamPrefixLineCache extends RenderSignature {
1662
1677
  text: string;
1663
1678
  tokenCount: number;
1664
- lines: readonly string[];
1679
+ // Private to the cache and never handed to callers (each frame copies it
1680
+ // into a fresh output array), so an advancing prefix appends in place.
1681
+ lines: string[];
1665
1682
  }
1666
1683
  /**
1667
1684
  * Per-token row cache for the *unfrozen tail* (PoC H). The tail re-lexes every
@@ -1717,7 +1734,9 @@ interface TailRenderRecorder {
1717
1734
  interface StreamingHighlightCache extends RenderSignature {
1718
1735
  lang: string | undefined;
1719
1736
  text: string;
1720
- lines: readonly string[];
1737
+ // Appended in place as the fence grows. Only ever returned to
1738
+ // #renderCodeBodyLines, which reads it synchronously and keeps no reference.
1739
+ lines: string[];
1721
1740
  stream: HighlightStreamSession;
1722
1741
  }
1723
1742
 
@@ -1801,6 +1820,7 @@ export class Markdown implements Component {
1801
1820
  // B+ capture plumbing: #renderContentLines records the last rendered paragraph row.
1802
1821
  #lastTailCapture?: { kind: "paragraph"; open: boolean; rowInput: string; rowRaw: string };
1803
1822
  #ignoreTight = false;
1823
+ #native?: { text: string; stream: boolean; node: NativeNode };
1804
1824
  setIgnoreTight(ignore: boolean): this {
1805
1825
  this.#ignoreTight = ignore;
1806
1826
  this.invalidate();
@@ -1882,10 +1902,7 @@ export class Markdown implements Component {
1882
1902
  // Blank replacement: render() early-returns before #lexTokens can see
1883
1903
  // the non-append edit, so drop the frozen stream state here or it
1884
1904
  // outlives the content it indexed.
1885
- this.#streamPrefixText = undefined;
1886
- this.#streamPrefixTokens = undefined;
1887
- this.#streamPrefixLineCache = undefined;
1888
- this.#tailRowCache = undefined;
1905
+ this.#dropStreamPrefix();
1889
1906
  // B+: the captured fast-path rows index the replaced content — drop
1890
1907
  // the recipe so a fresh stream cannot splice onto stale rows.
1891
1908
  this.#fastTail = undefined;
@@ -1900,6 +1917,23 @@ export class Markdown implements Component {
1900
1917
  this.#cachedLines = undefined;
1901
1918
  }
1902
1919
 
1920
+ /**
1921
+ * The unrendered source as an `md` node, `stream` while the streaming
1922
+ * (transient) cache is on. Append-only growth yields a fresh node of the
1923
+ * same kind and key whose text extends the previous one, which the
1924
+ * reconciler sends as `text append`.
1925
+ */
1926
+ describe(_cx: DescribeContext): NativeNode {
1927
+ const stream = this.#transientRenderCache;
1928
+ const cached = this.#native;
1929
+ if (cached?.text === this.#text && cached.stream === stream) return cached.node;
1930
+ // Sources are model text; OSC 8 / SGR bytes a caller spliced in are the terminal's to style, not ours.
1931
+ const source = this.#text.includes("\x1b") ? plainText(this.#text) : this.#text;
1932
+ const described = md(source, stream ? { stream: true } : undefined);
1933
+ this.#native = { text: this.#text, stream, node: described };
1934
+ return described;
1935
+ }
1936
+
1903
1937
  /**
1904
1938
  * Width-independent source prefix of the last render ending at a frozen
1905
1939
  * Markdown block boundary. Only meaningful while streaming (transient
@@ -1927,6 +1961,17 @@ export class Markdown implements Component {
1927
1961
  // source — drop the fast-path recipe so stale rows cannot be served
1928
1962
  // across the transition.
1929
1963
  this.#fastTail = undefined;
1964
+ if (!next) {
1965
+ // Finalize: the prefix row cache, tail row cache and open-fence
1966
+ // highlight stream are read only by transient renders, and each
1967
+ // rebuilds from scratch if the block ever streams again — release
1968
+ // them so settled blocks stop pinning rows and a native parser.
1969
+ // The frozen lex prefix survives only until the next final-mode
1970
+ // render consumes it (see #lexTokens / the L2 hit path).
1971
+ this.#streamPrefixLineCache = undefined;
1972
+ this.#tailRowCache = undefined;
1973
+ this.#streamingHighlightCache = undefined;
1974
+ }
1930
1975
  this.invalidate();
1931
1976
  }
1932
1977
 
@@ -1964,8 +2009,14 @@ export class Markdown implements Component {
1964
2009
  // happens exactly when the CR/ref-def trigger behind a false verdict
1965
2010
  // may have been deleted — never left stale, and never re-scanned on
1966
2011
  // frames where the memo is sound.
2012
+ const appendGrowth = this.#lastScanValid && this.#appendOnlySinceLastScan && text.length > this.#lastScanLength;
2013
+ // Final mode keeps the frozen prefix only while the text keeps growing
2014
+ // by appends (a stream rendered without the transient flag). A one-shot
2015
+ // or just-finalized render still lexes against any existing prefix but
2016
+ // then drops it, so settled blocks do not pin a token tree.
2017
+ const retainPrefix = this.#transientRenderCache || appendGrowth;
1967
2018
  let canStream: boolean;
1968
- if (this.#lastScanValid && this.#appendOnlySinceLastScan && text.length > this.#lastScanLength) {
2019
+ if (appendGrowth) {
1969
2020
  const delta = text.slice(this.#lastScanLength);
1970
2021
  if (
1971
2022
  !delta.includes("[") &&
@@ -1990,21 +2041,27 @@ export class Markdown implements Component {
1990
2041
  if (canStream && hasPrefix) {
1991
2042
  const tailTokens = lexDocument(refDefText);
1992
2043
  const tokens = [...prefixTokens, ...tailTokens];
1993
- this.#freezeStablePrefix(text, tokens, { preserveExisting: true });
2044
+ if (retainPrefix) this.#freezeStablePrefix(text, tokens, { preserveExisting: true });
2045
+ else this.#dropStreamPrefix();
1994
2046
  return tokens;
1995
2047
  }
1996
2048
  const tokens = lexDocument(text);
1997
- if (canStream) {
2049
+ if (canStream && retainPrefix) {
1998
2050
  this.#freezeStablePrefix(text, tokens, { preserveExisting: false });
1999
2051
  } else {
2000
- this.#streamPrefixText = undefined;
2001
- this.#streamPrefixTokens = undefined;
2002
- this.#streamPrefixLineCache = undefined;
2003
- this.#tailRowCache = undefined;
2052
+ this.#dropStreamPrefix();
2004
2053
  }
2005
2054
  return tokens;
2006
2055
  }
2007
2056
 
2057
+ /** Drop the frozen lex prefix and the transient row caches keyed on it. */
2058
+ #dropStreamPrefix(): void {
2059
+ this.#streamPrefixText = undefined;
2060
+ this.#streamPrefixTokens = undefined;
2061
+ this.#streamPrefixLineCache = undefined;
2062
+ this.#tailRowCache = undefined;
2063
+ }
2064
+
2008
2065
  // Freeze the largest run of leading blocks that end on a hard "\n\n" boundary
2009
2066
  // (complete and immutable under append-only growth) so the next streaming
2010
2067
  // render re-lexes only the unfrozen tail. Caller guarantees no CR / no
@@ -2032,12 +2089,7 @@ export class Markdown implements Component {
2032
2089
  return;
2033
2090
  }
2034
2091
 
2035
- if (!opts.preserveExisting) {
2036
- this.#streamPrefixText = undefined;
2037
- this.#streamPrefixTokens = undefined;
2038
- this.#streamPrefixLineCache = undefined;
2039
- this.#tailRowCache = undefined;
2040
- }
2092
+ if (!opts.preserveExisting) this.#dropStreamPrefix();
2041
2093
  }
2042
2094
 
2043
2095
  render(width: number): readonly string[] {
@@ -2166,9 +2218,12 @@ export class Markdown implements Component {
2166
2218
  );
2167
2219
  }
2168
2220
  // Splice onto the previous frame's rows (new array — parent may
2169
- // hold the old one).
2221
+ // hold the old one). One copy of the head, then the few new and
2222
+ // trailing rows, instead of spreading two intermediate slices.
2170
2223
  const prev = recipe.lines;
2171
- const fastResult = [...prev.slice(0, recipe.rowStart), ...fastRows, ...prev.slice(recipe.rowEnd)];
2224
+ const fastResult = prev.slice(0, recipe.rowStart);
2225
+ appendLines(fastResult, fastRows);
2226
+ for (let i = recipe.rowEnd; i < prev.length; i++) fastResult.push(prev[i]!);
2172
2227
  this.#cachedText = this.#text;
2173
2228
  this.#cachedWidth = width;
2174
2229
  this.#cachedLines = fastResult;
@@ -2220,6 +2275,10 @@ export class Markdown implements Component {
2220
2275
  cacheKey = this.#renderCacheKey(normalizedText, signature);
2221
2276
  const cached = renderCache.get(cacheKey);
2222
2277
  if (cached !== undefined) {
2278
+ // A final-mode hit right after finalize (or a non-append edit)
2279
+ // never reaches #lexTokens, which would otherwise consume and drop
2280
+ // the frozen lex prefix — release it here instead.
2281
+ if (!this.#appendOnlySinceLastScan) this.#dropStreamPrefix();
2223
2282
  // Populate L1 so subsequent calls from this instance are O(1) map lookup.
2224
2283
  this.#cachedText = this.#text;
2225
2284
  this.#cachedWidth = width;
@@ -2241,8 +2300,10 @@ export class Markdown implements Component {
2241
2300
  }
2242
2301
  const emptyLines = this.#renderEmptyPaddingLines(signature);
2243
2302
 
2244
- // Combine top padding, content, and bottom padding
2245
- const rawResult = [...emptyLines, ...contentLines, ...emptyLines];
2303
+ // Combine top padding, content, and bottom padding. contentLines is a
2304
+ // fresh per-render array (never cache-owned), so without vertical
2305
+ // padding it is handed out as-is rather than copied again.
2306
+ const rawResult = emptyLines.length === 0 ? contentLines : [...emptyLines, ...contentLines, ...emptyLines];
2246
2307
  const result = rawResult.length > 0 ? rawResult : [""];
2247
2308
 
2248
2309
  // Update caches and hand the array out by reference. Callers must not
@@ -2329,11 +2390,15 @@ export class Markdown implements Component {
2329
2390
  return this.#renderStreamingTail(tokens, 0, contentWidth, signature);
2330
2391
  }
2331
2392
 
2332
- const contentLines: string[] = [];
2393
+ // Prefix rows live in #streamPrefixLineCache.lines, which is private to
2394
+ // the cache: an advancing prefix appends its newly frozen rows in place
2395
+ // instead of re-snapshotting, and each frame's output is one fresh copy
2396
+ // of prefix + tail, so no array handed to a caller is ever mutated.
2333
2397
  const reusablePrefix = this.#matchingStreamPrefixLineCache(normalizedText, stableText, signature);
2398
+ let prefixLines: string[] = [];
2334
2399
  let renderedUntil = 0;
2335
2400
  if (reusablePrefix && reusablePrefix.tokenCount <= stableTokenCount) {
2336
- contentLines.push(...reusablePrefix.lines);
2401
+ prefixLines = reusablePrefix.lines;
2337
2402
  renderedUntil = reusablePrefix.tokenCount;
2338
2403
  }
2339
2404
 
@@ -2341,13 +2406,14 @@ export class Markdown implements Component {
2341
2406
  // Stable tokens render with full fidelity (syntax highlighting on)
2342
2407
  // so these cached rows byte-match the finalized render.
2343
2408
  this.#renderingStablePrefix = true;
2409
+ let frozenRows: string[];
2344
2410
  try {
2345
- contentLines.push(
2346
- ...this.#renderContentLines(tokens, renderedUntil, stableTokenCount, contentWidth, signature),
2347
- );
2411
+ frozenRows = this.#renderContentLines(tokens, renderedUntil, stableTokenCount, contentWidth, signature);
2348
2412
  } finally {
2349
2413
  this.#renderingStablePrefix = false;
2350
2414
  }
2415
+ if (prefixLines.length === 0) prefixLines = frozenRows;
2416
+ else appendLines(prefixLines, frozenRows);
2351
2417
  renderedUntil = stableTokenCount;
2352
2418
  }
2353
2419
 
@@ -2355,14 +2421,11 @@ export class Markdown implements Component {
2355
2421
  ...signature,
2356
2422
  text: stableText,
2357
2423
  tokenCount: stableTokenCount,
2358
- lines: contentLines.slice(),
2424
+ lines: prefixLines,
2359
2425
  };
2360
2426
 
2361
- if (renderedUntil < tokens.length) {
2362
- contentLines.push(...this.#renderStreamingTail(tokens, renderedUntil, contentWidth, signature));
2363
- }
2364
-
2365
- return contentLines;
2427
+ if (renderedUntil >= tokens.length) return prefixLines.slice();
2428
+ return prefixLines.concat(this.#renderStreamingTail(tokens, renderedUntil, contentWidth, signature));
2366
2429
  }
2367
2430
 
2368
2431
  #matchingStreamPrefixLineCache(
@@ -2403,7 +2466,7 @@ export class Markdown implements Component {
2403
2466
  if (cache !== undefined) {
2404
2467
  spliceEnd = this.#tailSpliceEnd(cache, start, signature, tokens);
2405
2468
  for (let i = start; i < spliceEnd; i++) {
2406
- out.push(...cache.rows[i - start]!);
2469
+ appendLines(out, cache.rows[i - start]!);
2407
2470
  }
2408
2471
  }
2409
2472
 
@@ -2416,7 +2479,7 @@ export class Markdown implements Component {
2416
2479
  nextTypes: new Array(tokens.length - spliceEnd).fill(undefined),
2417
2480
  };
2418
2481
  const fresh = this.#renderContentLines(tokens, spliceEnd, tokens.length, contentWidth, signature, recorder);
2419
- out.push(...fresh);
2482
+ const result = out.length === 0 ? fresh : out.concat(fresh);
2420
2483
 
2421
2484
  // Refresh the cache: keep entries for spliced tokens (their raws stay
2422
2485
  // valid), overlay the fresh entries, and re-derive the contiguous
@@ -2453,7 +2516,7 @@ export class Markdown implements Component {
2453
2516
  raws,
2454
2517
  nextTypes,
2455
2518
  };
2456
- return out;
2519
+ return result;
2457
2520
  }
2458
2521
 
2459
2522
  // Longest cache-spliceable prefix: every cached row from `start` up to
@@ -2658,7 +2721,7 @@ export class Markdown implements Component {
2658
2721
  // colors reach completed rows immediately; only the trailing partial
2659
2722
  // line stays unhighlighted.
2660
2723
  const lineEnd = tokenText.lastIndexOf("\n");
2661
- const completedLines = lineEnd >= 0 ? this.#highlightStreamingLines(tokenText.slice(0, lineEnd), lang) : null;
2724
+ const completedLines = lineEnd >= 0 ? this.#highlightStreamingLines(tokenText, lineEnd, lang) : null;
2662
2725
  if (completedLines) {
2663
2726
  for (const hlLine of completedLines) {
2664
2727
  addBodyLine(hlLine);
@@ -2708,21 +2771,28 @@ export class Markdown implements Component {
2708
2771
  }
2709
2772
 
2710
2773
  /**
2711
- * Highlight the completed (newline-terminated) prefix of a streaming code
2712
- * fence. Uses a stateful per-fence highlight stream so each render pushes
2713
- * only the newly completed lines, with output byte-identical to the
2714
- * whole-block `highlightCode` the finalized render performs. Returns null
2715
- * when no stream is available for `lang` (caller falls back to plain
2716
- * code-block styling).
2774
+ * Highlight the completed (newline-terminated) prefix `tokenText[0,
2775
+ * completedEnd)` of a streaming code fence. Uses a stateful per-fence
2776
+ * highlight stream so each render pushes only the newly completed lines,
2777
+ * with output byte-identical to the whole-block `highlightCode` the
2778
+ * finalized render performs. Returns null when no stream is available for
2779
+ * `lang` (caller falls back to plain code-block styling).
2717
2780
  */
2718
- #highlightStreamingLines(completedText: string, lang: string | undefined): readonly string[] | null {
2781
+ #highlightStreamingLines(
2782
+ tokenText: string,
2783
+ completedEnd: number,
2784
+ lang: string | undefined,
2785
+ ): readonly string[] | null {
2719
2786
  const signature = this.#activeRenderSignature;
2720
2787
  const cache = this.#streamingHighlightCache;
2788
+ // Cheap cursor/length and signature gates first; the prefix compare
2789
+ // (the only O(fence) check, needed because a different fence can reach
2790
+ // this cache) runs last.
2721
2791
  if (
2722
2792
  signature &&
2723
2793
  cache &&
2724
- completedText.startsWith(cache.text) &&
2725
- (cache.text.length === completedText.length || completedText.charCodeAt(cache.text.length) === 0x0a) &&
2794
+ cache.text.length <= completedEnd &&
2795
+ (cache.text.length === completedEnd || tokenText.charCodeAt(cache.text.length) === 0x0a) &&
2726
2796
  cache.lang === lang &&
2727
2797
  cache.width === signature.width &&
2728
2798
  cache.paddingX === signature.paddingX &&
@@ -2734,18 +2804,23 @@ export class Markdown implements Component {
2734
2804
  cache.hyperlinks === signature.hyperlinks &&
2735
2805
  cache.textSizing === signature.textSizing &&
2736
2806
  cache.bgColorProbe === signature.bgColorProbe &&
2737
- cache.headingProbe === signature.headingProbe
2807
+ cache.headingProbe === signature.headingProbe &&
2808
+ tokenText.startsWith(cache.text)
2738
2809
  ) {
2739
- if (completedText.length === cache.text.length) return cache.lines;
2810
+ const cachedEnd = cache.text.length;
2811
+ if (cachedEnd === completedEnd) return cache.lines;
2740
2812
  // Invariant: the stream has consumed `cache.text + "\n"`, so pushing
2741
2813
  // the added lines with a trailing newline advances it to
2742
- // `completedText + "\n"` — every fed line stays newline-terminated.
2743
- const addedText = completedText.slice(cache.text.length + 1);
2744
- const lines = cache.lines.concat(splitPushedHighlightLines(cache.stream.push(`${addedText}\n`)));
2745
- this.#streamingHighlightCache = { ...signature, lang, text: completedText, lines, stream: cache.stream };
2746
- return lines;
2814
+ // `completed + "\n"` — every fed line stays newline-terminated. The
2815
+ // signature gates above matched, so advancing the cache in place is
2816
+ // equivalent to rebuilding it, minus the whole-array copy per frame.
2817
+ const pushed = cache.stream.push(`${tokenText.slice(cachedEnd + 1, completedEnd)}\n`);
2818
+ appendLines(cache.lines, splitPushedHighlightLines(pushed));
2819
+ cache.text = tokenText.slice(0, completedEnd);
2820
+ return cache.lines;
2747
2821
  }
2748
2822
 
2823
+ const completedText = tokenText.slice(0, completedEnd);
2749
2824
  const stream = this.#createHighlightStream(lang);
2750
2825
  if (!stream) return null;
2751
2826
  const lines = splitPushedHighlightLines(stream.push(`${completedText}\n`));