@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,3 +1,4 @@
1
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
1
2
  import {
2
3
  type Component,
3
4
  Ellipsis,
@@ -33,6 +34,12 @@ export interface ExtensionAskDialogQuestion {
33
34
  recommended?: number;
34
35
  }
35
36
 
37
+ /** Prompt text and the images pasted into it (custom answers and notes). */
38
+ export interface AskDialogPromptValue {
39
+ text: string;
40
+ images?: ImageContent[];
41
+ }
42
+
36
43
  /** Submitted answer to one dialog question. */
37
44
  export interface ExtensionAskDialogResultItem {
38
45
  id: string;
@@ -41,7 +48,9 @@ export interface ExtensionAskDialogResultItem {
41
48
  multi: boolean;
42
49
  selectedOptions: string[];
43
50
  customInput?: string;
51
+ customInputImages?: ImageContent[];
44
52
  note?: string;
53
+ noteImages?: ImageContent[];
45
54
  timedOut?: boolean;
46
55
  }
47
56
 
@@ -75,9 +84,25 @@ import { editorKey, editorKeys } from "../chrome/keybinding-hints";
75
84
  import { formatKeyHint, formatKeyHints } from "../app-keybindings";
76
85
  import { OverlayPanel, PanelDivider, PanelRows } from "../chrome/overlay-box";
77
86
  import { handleTabSwitchKey } from "../chrome/selector-helpers";
87
+ import { col, node, span, text } from "../native/describe";
88
+ import {
89
+ type DescribeContext,
90
+ leafKey,
91
+ type NativeChild,
92
+ type NativeNode,
93
+ type NativeUiEvent,
94
+ type TspSpan,
95
+ } from "../native/node";
96
+ import { actionBar, actionButton } from "../native/overlay";
97
+ import { getKeybindings } from "../keybindings";
98
+ import { plainText } from "../native/spans";
78
99
 
79
100
  const OTHER_OPTION = "Other (type your own)";
80
101
  const SUBMIT_OPTION = "Submit";
102
+ const RECOMMENDED_SUFFIX = " (Recommended)";
103
+ /** Native labels: the free-text row and the answers-review tab. */
104
+ const OTHER_LABEL = "Other…";
105
+ const REVIEW_TAB = "Review";
81
106
 
82
107
  // Action rows appended by the guest race participant. An option sanitizing
83
108
  // to one of these must disambiguate identically on both sides, or the same
@@ -129,6 +154,8 @@ interface AskDialogCallbacks {
129
154
  onSubmit(result: ExtensionAskDialogSubmitResult): void;
130
155
  onCancel(): void;
131
156
  onPrompt(title: string, prefill?: string): Promise<string | undefined>;
157
+ /** Prompt that accepts pasted images; without it, prompts use `onPrompt`. */
158
+ onImagePrompt?(title: string, prefill: AskDialogPromptValue | undefined): Promise<AskDialogPromptValue | undefined>;
132
159
  }
133
160
 
134
161
  interface AskDialogInputGuard {
@@ -151,7 +178,9 @@ interface AskDialogOptions {
151
178
  interface QuestionState {
152
179
  selectedOptions: Set<string>;
153
180
  customInput: string | undefined;
181
+ customInputImages: ImageContent[] | undefined;
154
182
  note: string | undefined;
183
+ noteImages: ImageContent[] | undefined;
155
184
  noteRowKey: string | undefined;
156
185
  cursorIndex: number;
157
186
  scrollOffset: number;
@@ -186,9 +215,13 @@ function clamp(value: number, min: number, max: number): number {
186
215
  return Math.max(min, Math.min(value, max));
187
216
  }
188
217
 
218
+ /** Full tab label; the ANSI tab strip and review list clip it to a chip. */
219
+ function questionTabName(question: ExtensionAskDialogQuestion, index: number): string {
220
+ return replaceTabs(question.header?.trim() || sanitizeCarriageReturns(question.id) || `Q${index + 1}`);
221
+ }
222
+
189
223
  function questionTabLabel(question: ExtensionAskDialogQuestion, index: number): string {
190
- const base = question.header?.trim() || sanitizeCarriageReturns(question.id) || `Q${index + 1}`;
191
- return truncateToWidth(replaceTabs(base), MAX_HEADER_CHIP_WIDTH, Ellipsis.Unicode);
224
+ return truncateToWidth(questionTabName(question, index), MAX_HEADER_CHIP_WIDTH, Ellipsis.Unicode);
192
225
  }
193
226
 
194
227
  function wrapQuestionTitle(question: ExtensionAskDialogQuestion, width: number): string[] {
@@ -323,10 +356,11 @@ function normalizedInlineInput(input: string): string {
323
356
  * participant answers. State and results keep originals.
324
357
  */
325
358
  function displayOptionLabels(question: ExtensionAskDialogQuestion): string[] {
326
- const recommendedSuffix = " (Recommended)";
327
359
  const badged = question.options.map((option, index) => {
328
360
  const base = sanitizeCarriageReturns(option.label);
329
- return question.recommended === index && !base.endsWith(recommendedSuffix) ? `${base}${recommendedSuffix}` : base;
361
+ return question.recommended === index && !base.endsWith(RECOMMENDED_SUFFIX)
362
+ ? `${base}${RECOMMENDED_SUFFIX}`
363
+ : base;
330
364
  });
331
365
  return disambiguateDisplayLabels(badged, [OTHER_OPTION, ...GUEST_ACTION_LABELS]);
332
366
  }
@@ -350,8 +384,43 @@ function renderAnswerSummary(question: ExtensionAskDialogQuestion, state: Questi
350
384
  return selected[0] ?? theme.fg("warning", "unanswered");
351
385
  }
352
386
 
387
+ /** {@link renderAnswerSummary} as spans for the native review list. */
388
+ function describeAnswer(question: ExtensionAskDialogQuestion, state: QuestionState): readonly TspSpan[] {
389
+ if (state.selectedOptions.size === 0 && state.customInput === undefined) return [span("unanswered", "warning")];
390
+ const spans = [span(plainText(renderAnswerSummary(question, state)))];
391
+ const note = noteForSubmittedAnswer(question, state)?.trim();
392
+ if (note) spans.push(span(` · Note: ${normalizedInlineInput(note)}`, "muted"));
393
+ return spans;
394
+ }
395
+
396
+ /** Native `item.detail` of an option row: its description, the typed custom answer, and its note. */
397
+ function describeRowDetail(
398
+ rowItem: QuestionRow,
399
+ question: ExtensionAskDialogQuestion,
400
+ state: QuestionState,
401
+ ): readonly TspSpan[] | undefined {
402
+ const spans: TspSpan[] = [];
403
+ if (rowItem.kind === "option") {
404
+ const description = question.options[rowItem.optionIndex ?? -1]?.description?.trim();
405
+ if (description) spans.push(span(normalizedInlineInput(description), "muted"));
406
+ } else if (state.customInput !== undefined) {
407
+ spans.push(span(`“${normalizedInlineInput(state.customInput)}”`, "muted"));
408
+ }
409
+ if (state.note && state.noteRowKey === rowItem.key) {
410
+ spans.push(span(`${spans.length > 0 ? " · " : ""}✎ ${normalizedInlineInput(state.note)}`, "success"));
411
+ }
412
+ return spans.length > 0 ? spans : undefined;
413
+ }
414
+
415
+ /** Normalize the result of either prompt callback. */
416
+ function splitPromptInput(input: string | AskDialogPromptValue): { text: string; images: ImageContent[] | undefined } {
417
+ if (typeof input === "string") return { text: input, images: undefined };
418
+ return { text: input.text, images: input.images?.length ? input.images : undefined };
419
+ }
420
+
353
421
  function clearNote(state: QuestionState): void {
354
422
  state.note = undefined;
423
+ state.noteImages = undefined;
355
424
  state.noteRowKey = undefined;
356
425
  }
357
426
 
@@ -490,6 +559,14 @@ export class AskDialogComponent implements Component {
490
559
  #contentWidth = 76;
491
560
  #headerExpandable = false;
492
561
  #descExpandable = false;
562
+ /** Memoized native description; dropped whenever dialog state may have changed. */
563
+ #native: NativeNode | undefined;
564
+ /** Input-guard blocked state {@link #native} was described with (the guard flips outside this dialog). */
565
+ #nativeBlocked = false;
566
+ /** When the countdown runs out (epoch ms), for the native ring. */
567
+ #countdownDeadline = 0;
568
+ /** Once-a-second re-describe while the native countdown ring is shown. */
569
+ #ringTick: NodeJS.Timeout | undefined;
493
570
  readonly #panel: OverlayPanel;
494
571
  readonly #headerRegion: PanelRows;
495
572
  readonly #bodyRegion: PanelRows;
@@ -508,7 +585,9 @@ export class AskDialogComponent implements Component {
508
585
  return {
509
586
  selectedOptions: new Set<string>(),
510
587
  customInput: undefined,
588
+ customInputImages: undefined,
511
589
  note: undefined,
590
+ noteImages: undefined,
512
591
  noteRowKey: undefined,
513
592
  cursorIndex: clamp(recommended ?? 0, 0, maxIndex),
514
593
  scrollOffset: 0,
@@ -522,6 +601,8 @@ export class AskDialogComponent implements Component {
522
601
  options.tui,
523
602
  seconds => {
524
603
  this.#remainingSeconds = seconds;
604
+ // Fires on every (re)start: the native ring's deadline.
605
+ this.#countdownDeadline = Date.now() + seconds * 1000;
525
606
  },
526
607
  () => this.#handleTimeout(),
527
608
  );
@@ -539,6 +620,7 @@ export class AskDialogComponent implements Component {
539
620
  }
540
621
 
541
622
  invalidate(): void {
623
+ this.#native = undefined;
542
624
  this.#stableHeight = undefined;
543
625
  this.#previewCache.clear();
544
626
  this.#overflowLayouts = new WeakMap();
@@ -549,6 +631,7 @@ export class AskDialogComponent implements Component {
549
631
  dispose(): void {
550
632
  this.#closed = true;
551
633
  this.#countdown?.dispose();
634
+ this.#stopRingTick();
552
635
  this.#panel.dispose();
553
636
  }
554
637
  /**
@@ -557,23 +640,30 @@ export class AskDialogComponent implements Component {
557
640
  * expand transcript tools.
558
641
  */
559
642
  toggleQuestionExpansion(): boolean {
560
- if (this.#closed || this.#isSubmitTab()) return false;
561
- const question = this.#questions[this.#currentQuestionIndex()];
562
- if (!question) return false;
563
- const headerOverflows = wrapQuestionTitle(question, this.#contentWidth).length > MAX_HEADER_ROWS;
564
- const descOverflows = questionDescriptionsOverflow(question, this.#contentWidth);
565
- if (!headerOverflows && !descOverflows && !this.#expanded) return false;
643
+ if (!this.#canToggleExpansion()) return false;
566
644
  this.#expanded = !this.#expanded;
567
645
  this.invalidate();
568
646
  this.#requestRender();
569
647
  return true;
570
648
  }
571
649
 
650
+ #canToggleExpansion(): boolean {
651
+ if (this.#closed || this.#isSubmitTab()) return false;
652
+ const question = this.#questions[this.#currentQuestionIndex()];
653
+ if (!question) return false;
654
+ if (this.#expanded) return true;
655
+ return (
656
+ wrapQuestionTitle(question, this.#contentWidth).length > MAX_HEADER_ROWS ||
657
+ questionDescriptionsOverflow(question, this.#contentWidth)
658
+ );
659
+ }
660
+
572
661
  handleInput(keyData: string): void {
573
662
  if (this.#closed || this.#promptActive) return;
574
663
  // Reset the inactivity countdown on any key that reaches past the
575
664
  // closed/prompt guards, matching HookSelector/HookInput semantics.
576
665
  this.#countdown?.reset();
666
+ this.#native = undefined;
577
667
  if (matchesSelectCancel(keyData)) {
578
668
  this.#finishCancel();
579
669
  return;
@@ -636,6 +726,291 @@ export class AskDialogComponent implements Component {
636
726
  return this.#panel.render(width);
637
727
  }
638
728
 
729
+ /**
730
+ * A bottom-anchored glass sheet over the composer (the `overlay` hoists into
731
+ * the terminal's layer; the dialog's own slot in the dock stays empty).
732
+ */
733
+ describe(cx: DescribeContext): NativeNode {
734
+ const inputGuard = this.options.inputGuard;
735
+ // Same draft-cursor mirroring as render(): the proxied draft describes itself next to this dialog.
736
+ inputGuard?.syncPresentation?.();
737
+ const blocked = inputGuard?.isBlocked() ?? false;
738
+ const ring = this.#countdown !== undefined && cx.supports("meter");
739
+ if (ring) this.#startRingTick();
740
+ if (this.#native && this.#nativeBlocked === blocked) return this.#native;
741
+ const children: NativeChild[] = [];
742
+ const head = this.#describeHead(ring);
743
+ if (head) children.push(head);
744
+ if (this.#isSubmitTab()) this.#describeSubmitBody(children);
745
+ else this.#describeQuestionBody(children);
746
+ children.push(this.#describeActions(blocked));
747
+ const sheet = node(
748
+ "overlay",
749
+ { role: "omp.overlay.ask", anchor: "bottom", size: "md", modal: true },
750
+ [col(children, { gap: "md" })],
751
+ "sheet",
752
+ );
753
+ this.#native = col([sheet]);
754
+ this.#nativeBlocked = blocked;
755
+ return this.#native;
756
+ }
757
+
758
+ /** Question tabs (plus Review) and the countdown: a ring when the terminal draws meters, else a clocked `elapsed`. */
759
+ #describeHead(ring: boolean): NativeNode | undefined {
760
+ const children: NativeChild[] = [];
761
+ if (this.#hasSubmitTab()) {
762
+ const items = this.#questions.map((question, index) => ({
763
+ id: String(index),
764
+ label: questionTabName(question, index),
765
+ }));
766
+ items.push({ id: "submit", label: REVIEW_TAB });
767
+ const active = this.#isSubmitTab() ? "submit" : String(this.#activeTabIndex);
768
+ children.push(
769
+ node("tabs", { items, active, role: "omp.ask.questions", actions: { click: "select" } }, undefined, "tabs"),
770
+ );
771
+ }
772
+ if (this.#countdown) {
773
+ children.push(node("spacer", { grow: 1 }));
774
+ if (ring) {
775
+ const total = Math.max(1, this.options.timeout ?? 1);
776
+ const left = Math.max(0, this.#countdownDeadline - Date.now());
777
+ children.push(
778
+ node(
779
+ "meter",
780
+ {
781
+ value: Math.round((left / total) * 1000) / 1000,
782
+ style: "ring",
783
+ size: "sm",
784
+ label: `${Math.ceil(left / 1000)}s`,
785
+ title: "Answers the recommended option when the time runs out",
786
+ },
787
+ undefined,
788
+ "timer",
789
+ ),
790
+ );
791
+ } else {
792
+ children.push(this.#countdown.describe());
793
+ }
794
+ }
795
+ if (children.length === 0) return undefined;
796
+ return node("row", { role: "omp.ask.head", gap: "sm", align: "center" }, children, "head");
797
+ }
798
+
799
+ /** Question tab: the question as markdown and its answers as radio/check rows (label, description, badge). */
800
+ #describeQuestionBody(children: NativeChild[]): void {
801
+ const active = this.#activeQuestionState();
802
+ if (!active) return;
803
+ const { question, state } = active;
804
+ const rows = this.#questionRows(question);
805
+ state.cursorIndex = clamp(state.cursorIndex, 0, Math.max(0, rows.length - 1));
806
+ children.push(
807
+ node(
808
+ "md",
809
+ { text: replaceTabs(sanitizeCarriageReturns(question.question)), role: "omp.ask.question" },
810
+ undefined,
811
+ "question",
812
+ ),
813
+ );
814
+ const optionRole = question.multi ? "omp.ask.check" : "omp.ask.option";
815
+ const items = rows.map(rowItem => {
816
+ const option = rowItem.kind === "option" ? question.options[rowItem.optionIndex ?? -1] : undefined;
817
+ const checked =
818
+ rowItem.kind === "other"
819
+ ? state.customInput !== undefined
820
+ : option !== undefined && state.selectedOptions.has(option.label);
821
+ const recommended = rowItem.kind === "option" && question.recommended === rowItem.optionIndex;
822
+ // The badge replaces the ANSI `(Recommended)` suffix.
823
+ const label =
824
+ rowItem.kind === "other"
825
+ ? OTHER_LABEL
826
+ : recommended && rowItem.label.endsWith(RECOMMENDED_SUFFIX)
827
+ ? rowItem.label.slice(0, -RECOMMENDED_SUFFIX.length)
828
+ : rowItem.label;
829
+ const detail =
830
+ describeRowDetail(rowItem, question, state) ??
831
+ (rowItem.kind === "other" ? [span("Type your own answer", "dim")] : undefined);
832
+ return node(
833
+ "item",
834
+ {
835
+ // Inline code runs mono, as the ANSI row renders inline markdown.
836
+ label: label
837
+ .split(/(`[^`]+`)/)
838
+ .flatMap(piece =>
839
+ piece === ""
840
+ ? []
841
+ : piece.length > 2 && piece.startsWith("`") && piece.endsWith("`")
842
+ ? [span(piece.slice(1, -1), "code")]
843
+ : [span(piece)],
844
+ ),
845
+ role: rowItem.kind === "other" ? "omp.ask.other" : optionRole,
846
+ ...(detail ? { detail } : {}),
847
+ ...(recommended ? { value: [span("Recommended", "accent")] } : {}),
848
+ ...(checked ? { icon: "check", tone: "success" as const } : {}),
849
+ },
850
+ undefined,
851
+ rowItem.key,
852
+ );
853
+ });
854
+ // Keyed per question: a click aimed at one question's list never lands on the next after it advances.
855
+ children.push(
856
+ node(
857
+ "list",
858
+ { selected: rows[state.cursorIndex]?.key ?? null, role: "omp.ask.options" },
859
+ items,
860
+ `q${this.#currentQuestionIndex()}`,
861
+ ),
862
+ );
863
+ const highlighted = rows[state.cursorIndex];
864
+ const option = highlighted?.kind === "option" ? question.options[highlighted.optionIndex ?? -1] : undefined;
865
+ if (option?.preview?.trim()) {
866
+ children.push(node("md", { text: replaceTabs(option.preview) }, undefined, "preview"));
867
+ }
868
+ }
869
+
870
+ /** Review tab: every answer as key/value pairs; the action bar's Submit sends them. */
871
+ #describeSubmitBody(children: NativeChild[]): void {
872
+ const unanswered = this.#unansweredCount();
873
+ if (unanswered > 0) {
874
+ const noun = `question${unanswered === 1 ? "" : "s"}`;
875
+ children.push(
876
+ text([span(`${unanswered} unanswered ${noun}; ${formatKeyHint("enter")} still submits.`, "warning")]),
877
+ );
878
+ }
879
+ const answers: { k: string; v: readonly TspSpan[] }[] = [];
880
+ for (let index = 0; index < this.#questions.length; index++) {
881
+ const question = this.#questions[index];
882
+ const state = this.#states[index];
883
+ if (!question || !state) continue;
884
+ answers.push({ k: `${index + 1}. ${questionTabName(question, index)}`, v: describeAnswer(question, state) });
885
+ }
886
+ children.push(node("kv", { items: answers }, undefined, "answers"));
887
+ }
888
+
889
+ /**
890
+ * The action bar: Submit ⏎ (Enter's action on this tab; `Next` while more
891
+ * questions follow), Note n, Skip esc. Keyboard-only keys live in the
892
+ * buttons' tooltips; a draft that owns input shows its hint instead.
893
+ */
894
+ #describeActions(blocked: boolean): NativeNode {
895
+ const cancelKey = getKeybindings().getKeys("tui.select.cancel")[0];
896
+ const skip = actionButton("Skip", "cancel", cancelKey ? { keys: cancelKey } : {});
897
+ const inputGuard = this.options.inputGuard;
898
+ if (blocked && inputGuard) {
899
+ return actionBar([text([span(plainText(inputGuard.hint), "muted")], { truncate: "end" }), null, skip]);
900
+ }
901
+ const moves = ["up/down move"];
902
+ if (this.#hasSubmitTab()) moves.push("tab switches question");
903
+ if (this.#isSubmitTab()) {
904
+ const submit = actionButton("Submit", "submit", {
905
+ keys: "enter",
906
+ tone: "accent",
907
+ title: "Submit answers enter",
908
+ });
909
+ return actionBar([null, skip, submit]);
910
+ }
911
+ const question = this.#questions[this.#currentQuestionIndex()];
912
+ const last = this.#questions.length === 1;
913
+ const enterLabel = last ? "Submit" : "Next";
914
+ const tips = question?.multi ? ["space toggles", ...moves] : moves;
915
+ const submit = actionButton(enterLabel, "submit", {
916
+ keys: "enter",
917
+ tone: "accent",
918
+ title: `${enterLabel} enter · ${tips.join(" · ")}`,
919
+ });
920
+ const note = actionButton("Note", "note", { keys: "n", title: `Add a note to the highlighted answer n` });
921
+ return actionBar([null, note, skip, submit]);
922
+ }
923
+
924
+ /**
925
+ * Pointer actions: a tab click switches to it; a click on an answer row is
926
+ * Enter on it (Space for a multi-select option, so it toggles; its
927
+ * double-click `activate` is Enter); the action bar's Submit, Note and Skip
928
+ * run exactly what Enter, `n` and Esc run on the highlighted row.
929
+ */
930
+ handleNativeEvent(event: NativeUiEvent): void {
931
+ if (this.#closed || this.#promptActive) return;
932
+ if (event.type === "action") {
933
+ if (event.act === "cancel") {
934
+ this.#finishCancel();
935
+ return;
936
+ }
937
+ if (this.options.inputGuard?.isBlocked()) return;
938
+ if (event.act === "submit") this.#pressEnter();
939
+ else if (event.act === "note") this.#pressNote();
940
+ return;
941
+ }
942
+ if (event.type !== "select" && event.type !== "activate") return;
943
+ if (this.options.inputGuard?.isBlocked()) return;
944
+ const key = leafKey(event.key);
945
+ if (key === "tabs") {
946
+ if (!this.#hasSubmitTab()) return;
947
+ const index = event.item === "submit" ? this.#submitTabIndex() : Number(event.item);
948
+ if (!Number.isInteger(index) || index < 0 || index > this.#submitTabIndex()) return;
949
+ this.#countdown?.reset();
950
+ this.#activeTabIndex = index;
951
+ this.#submitScrollOffset = 0;
952
+ this.#requestRender();
953
+ return;
954
+ }
955
+ if (this.#isSubmitTab() || key !== `q${this.#currentQuestionIndex()}`) return;
956
+ const active = this.#activeQuestionState();
957
+ if (!active) return;
958
+ const { question, state } = active;
959
+ const rows = this.#questionRows(question);
960
+ const index = rows.findIndex(rowItem => rowItem.key === event.item);
961
+ const rowItem = rows[index];
962
+ if (!rowItem) return;
963
+ this.#countdown?.reset();
964
+ state.cursorIndex = index;
965
+ state.manualScroll = false;
966
+ const toggles = event.type === "select" && question.multi === true && rowItem.kind === "option";
967
+ this.#commitRow(question, state, rowItem, !toggles);
968
+ this.#requestRender();
969
+ }
970
+
971
+ /** Enter: submit on the Review tab, else commit the highlighted row. */
972
+ #pressEnter(): void {
973
+ this.#countdown?.reset();
974
+ if (this.#isSubmitTab()) {
975
+ this.#finishSubmit();
976
+ return;
977
+ }
978
+ const active = this.#activeQuestionState();
979
+ const rowItem = active ? this.#questionRows(active.question)[active.state.cursorIndex] : undefined;
980
+ if (!active || !rowItem) return;
981
+ this.#commitRow(active.question, active.state, rowItem, true);
982
+ this.#requestRender();
983
+ }
984
+
985
+ /** `n`: a note on the highlighted row (questions only). */
986
+ #pressNote(): void {
987
+ if (this.#isSubmitTab()) return;
988
+ this.#countdown?.reset();
989
+ const active = this.#activeQuestionState();
990
+ const rowItem = active ? this.#questionRows(active.question)[active.state.cursorIndex] : undefined;
991
+ if (!active || !rowItem) return;
992
+ void this.#promptForNote(active.question, active.state, rowItem);
993
+ }
994
+
995
+ /** Re-describe once a second while a countdown ring is shown (the ring's value is data, not a terminal clock). */
996
+ #startRingTick(): void {
997
+ if (this.#ringTick || this.#closed) return;
998
+ this.#ringTick = setInterval(() => {
999
+ if (this.#closed) {
1000
+ this.#stopRingTick();
1001
+ return;
1002
+ }
1003
+ this.#requestRender();
1004
+ }, 1000);
1005
+ this.#ringTick.unref?.();
1006
+ }
1007
+
1008
+ #stopRingTick(): void {
1009
+ if (!this.#ringTick) return;
1010
+ clearInterval(this.#ringTick);
1011
+ this.#ringTick = undefined;
1012
+ }
1013
+
639
1014
  #dialogHeight(width: number, termRows: number): number {
640
1015
  const key = `${width}:${termRows}:${this.#expanded ? 1 : 0}`;
641
1016
  if (this.#stableHeight?.key === key) return this.#stableHeight.total;
@@ -713,6 +1088,7 @@ export class AskDialogComponent implements Component {
713
1088
  }
714
1089
 
715
1090
  #requestRender(): void {
1091
+ this.#native = undefined;
716
1092
  this.options.tui?.requestRender();
717
1093
  }
718
1094
 
@@ -841,6 +1217,16 @@ export class AskDialogComponent implements Component {
841
1217
  const isEnter = matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n";
842
1218
  const isSpace = matchesKey(keyData, "space") || keyData === " ";
843
1219
  if (!isEnter && !(question.multi && isSpace)) return;
1220
+ this.#commitRow(question, state, rowItem, isEnter);
1221
+ }
1222
+
1223
+ /** Enter (`isEnter`) or multi-select Space on a row: toggle, select, or prompt for its answer. */
1224
+ #commitRow(
1225
+ question: ExtensionAskDialogQuestion,
1226
+ state: QuestionState,
1227
+ rowItem: QuestionRow,
1228
+ isEnter: boolean,
1229
+ ): void {
844
1230
  if (rowItem.kind === "other") {
845
1231
  void this.#promptForCustomInput(question, state, rowItem);
846
1232
  return;
@@ -867,6 +1253,7 @@ export class AskDialogComponent implements Component {
867
1253
  }
868
1254
  state.selectedOptions = new Set([option.label]);
869
1255
  state.customInput = undefined;
1256
+ state.customInputImages = undefined;
870
1257
  clearNoteUnlessRow(state, rowItem.key);
871
1258
  this.#advanceAfterQuestion();
872
1259
  }
@@ -904,6 +1291,19 @@ export class AskDialogComponent implements Component {
904
1291
  this.#requestRender();
905
1292
  }
906
1293
 
1294
+ /**
1295
+ * Open the host prompt, image-capable when available. Returns the callback's promise so each
1296
+ * caller keeps one `await` before clearing `#promptActive`, which the host's restore relies on.
1297
+ */
1298
+ #openPrompt(
1299
+ title: string,
1300
+ prefill: AskDialogPromptValue | undefined,
1301
+ ): Promise<string | AskDialogPromptValue | undefined> {
1302
+ return this.callbacks.onImagePrompt
1303
+ ? this.callbacks.onImagePrompt(title, prefill)
1304
+ : this.callbacks.onPrompt(title, prefill?.text);
1305
+ }
1306
+
907
1307
  async #promptForCustomInput(
908
1308
  question: ExtensionAskDialogQuestion,
909
1309
  state: QuestionState,
@@ -911,18 +1311,21 @@ export class AskDialogComponent implements Component {
911
1311
  ): Promise<void> {
912
1312
  this.#promptActive = true;
913
1313
  try {
914
- const input = await this.callbacks.onPrompt(
915
- boundPromptTitle("Custom answer: ", question.question),
916
- state.customInput,
917
- );
918
- if (input === undefined || this.#closed) return;
919
- if (input.trim() === "") {
1314
+ const title = boundPromptTitle("Custom answer: ", question.question);
1315
+ const prefill =
1316
+ state.customInput === undefined ? undefined : { text: state.customInput, images: state.customInputImages };
1317
+ const result = await this.#openPrompt(title, prefill);
1318
+ if (result === undefined || this.#closed) return;
1319
+ const input = splitPromptInput(result);
1320
+ if (input.text.trim() === "") {
920
1321
  // Submitting an empty value unselects the custom answer.
921
1322
  state.customInput = undefined;
1323
+ state.customInputImages = undefined;
922
1324
  clearNoteIfRow(state, rowItem.key);
923
1325
  return;
924
1326
  }
925
- state.customInput = input;
1327
+ state.customInput = input.text;
1328
+ state.customInputImages = input.images;
926
1329
  if (!question.multi) {
927
1330
  state.selectedOptions.clear();
928
1331
  clearNoteUnlessRow(state, rowItem.key);
@@ -947,12 +1350,15 @@ export class AskDialogComponent implements Component {
947
1350
  ): Promise<void> {
948
1351
  this.#promptActive = true;
949
1352
  try {
950
- const input = await this.callbacks.onPrompt(
951
- boundPromptTitle(`Note for ${rowItem.label}: `, question.question),
952
- state.noteRowKey === rowItem.key ? state.note : undefined,
953
- );
954
- if (input === undefined || this.#closed) return;
955
- state.note = input;
1353
+ const title = boundPromptTitle(`Note for ${rowItem.label}: `, question.question);
1354
+ const isReedit = state.noteRowKey === rowItem.key;
1355
+ const prefill =
1356
+ isReedit && state.note !== undefined ? { text: state.note, images: state.noteImages } : undefined;
1357
+ const result = await this.#openPrompt(title, prefill);
1358
+ if (result === undefined || this.#closed) return;
1359
+ const note = splitPromptInput(result);
1360
+ state.note = note.text;
1361
+ state.noteImages = note.images;
956
1362
  state.noteRowKey = rowItem.key;
957
1363
  } finally {
958
1364
  this.#promptActive = false;
@@ -1162,6 +1568,7 @@ export class AskDialogComponent implements Component {
1162
1568
  if (this.#closed) return;
1163
1569
  this.#closed = true;
1164
1570
  this.#countdown?.dispose();
1571
+ this.#stopRingTick();
1165
1572
  this.callbacks.onSubmit({ kind: "submit", results: this.#buildResults() });
1166
1573
  }
1167
1574
 
@@ -1169,6 +1576,7 @@ export class AskDialogComponent implements Component {
1169
1576
  if (this.#closed) return;
1170
1577
  this.#closed = true;
1171
1578
  this.#countdown?.dispose();
1579
+ this.#stopRingTick();
1172
1580
  this.callbacks.onCancel();
1173
1581
  }
1174
1582
 
@@ -1181,6 +1589,7 @@ export class AskDialogComponent implements Component {
1181
1589
  const selectedOptions = question.options
1182
1590
  .map(option => option.label)
1183
1591
  .filter(label => state.selectedOptions.has(label));
1592
+ const note = noteForSubmittedAnswer(question, state);
1184
1593
  results.push({
1185
1594
  id: question.id,
1186
1595
  question: question.question,
@@ -1188,7 +1597,9 @@ export class AskDialogComponent implements Component {
1188
1597
  multi: question.multi ?? false,
1189
1598
  selectedOptions,
1190
1599
  customInput: state.customInput,
1191
- note: noteForSubmittedAnswer(question, state),
1600
+ customInputImages: state.customInputImages,
1601
+ note,
1602
+ noteImages: note === undefined ? undefined : state.noteImages,
1192
1603
  timedOut: state.timedOut || undefined,
1193
1604
  });
1194
1605
  }
@@ -2,10 +2,14 @@ import { CancellableLoader, Container, Spacer, Text, type TUI } from "../index";
2
2
  import type { Theme } from "../theme/theme";
3
3
  import { DynamicBorder } from "../chrome/dynamic-border";
4
4
  import { editorKey } from "../chrome/keybinding-hints";
5
+ import type { NativeNode } from "../native/node";
6
+ import { card } from "../native/describe";
7
+ import { actionHint, hintsRow } from "../native/overlay";
5
8
 
6
9
  /** Loader wrapped with borders for hook UI */
7
10
  export class BorderedLoader extends Container {
8
11
  #loader: CancellableLoader;
12
+ #native: NativeNode | undefined;
9
13
 
10
14
  constructor(tui: TUI, theme: Theme, message: string) {
11
15
  super();
@@ -32,6 +36,19 @@ export class BorderedLoader extends Container {
32
36
  this.#loader.onAbort = fn;
33
37
  }
34
38
 
39
+ override invalidate(): void {
40
+ this.#native = undefined;
41
+ super.invalidate();
42
+ }
43
+
44
+ override describe(): NativeNode {
45
+ this.#native ??= card({ role: "omp.hook.loader" }, [
46
+ this.#loader,
47
+ hintsRow([actionHint("tui.select.cancel", "cancel")]),
48
+ ]);
49
+ return this.#native;
50
+ }
51
+
35
52
  handleInput(data: string): void {
36
53
  this.#loader.handleInput(data);
37
54
  }