@astralyn/pi 0.99.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/CHANGELOG.md +39 -3
  2. package/dist/bundle/chunks/anthropic-messages-CG2BSDQS.js +6 -0
  3. package/dist/bundle/chunks/anthropic.js +2 -2
  4. package/dist/bundle/chunks/{azure-openai-responses-RPBERDXD.js → azure-openai-responses-PBA4QKCM.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/{chunk-ABVRAYXT.js → chunk-6UFFVA7M.js} +2 -2
  7. package/dist/bundle/chunks/chunk-GIG5IHES.js +2 -0
  8. package/dist/bundle/chunks/{chunk-VW2P3X6P.js → chunk-HESRDUKO.js} +414 -666
  9. package/dist/bundle/chunks/{chunk-A576H74Y.js → chunk-PFZKRUMO.js} +1 -1
  10. package/dist/bundle/chunks/chunk-SGLYRVQL.js +2 -0
  11. package/dist/bundle/chunks/chunk-TRHTWM3C.js +2 -0
  12. package/dist/bundle/chunks/{chunk-DEWXBB6W.js → chunk-V66VGGOM.js} +1 -1
  13. package/dist/bundle/chunks/chunk-Z2RKSI3O.js +2 -0
  14. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-ZYSAFXXL.js → cloudflare-workers-ai-system-one-YB7XPPP4.js} +1 -1
  15. package/dist/bundle/chunks/codemode-worker.js +67 -8
  16. package/dist/bundle/chunks/github-copilot.js +1 -1
  17. package/dist/bundle/chunks/{google-generative-ai-SDNQVVXA.js → google-generative-ai-V5ZM5MCL.js} +1 -1
  18. package/dist/bundle/chunks/{google-vertex-LZXOLBIO.js → google-vertex-22THOFP5.js} +1 -1
  19. package/dist/bundle/chunks/{llama-cpp-classify-7IYGGPND.js → llama-cpp-classify-R7DQLTQN.js} +1 -1
  20. package/dist/bundle/chunks/{mistral-conversations-VYKPSSAM.js → mistral-conversations-WHSCJJWP.js} +1 -1
  21. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  22. package/dist/bundle/chunks/{openai-codex-responses-HZ4VUDXD.js → openai-codex-responses-N7GDPBPP.js} +1 -1
  23. package/dist/bundle/chunks/openai-codex.js +1 -1
  24. package/dist/bundle/chunks/{openai-completions-SKDGF6OE.js → openai-completions-RGGQK5UW.js} +1 -1
  25. package/dist/bundle/chunks/{openai-responses-FPML4SSG.js → openai-responses-3ZLD5JYI.js} +1 -1
  26. package/dist/bundle/chunks/{openrouter-images-5524IQHX.js → openrouter-images-YP3YVB54.js} +1 -1
  27. package/dist/bundle/chunks/openrouter.js +1 -1
  28. package/dist/bundle/chunks/radius.js +1 -1
  29. package/dist/bundle/chunks/{typesafe-system-one-Z2CDLKEY.js → typesafe-system-one-RREJAKWB.js} +1 -1
  30. package/dist/bundle/chunks/virtual-modules-4RQ2L6W4.js +2 -0
  31. package/dist/bundle/cli.js +1 -1
  32. package/dist/bundle/index.js +1 -1
  33. package/dist/bundle/rpc-entry.js +1 -1
  34. package/dist/cli/args.js +1 -1
  35. package/dist/cli/args.js.map +1 -1
  36. package/dist/config.d.ts +5 -8
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +16 -13
  39. package/dist/config.js.map +1 -1
  40. package/dist/core/agent-session.d.ts +21 -1
  41. package/dist/core/agent-session.d.ts.map +1 -1
  42. package/dist/core/agent-session.js +64 -11
  43. package/dist/core/agent-session.js.map +1 -1
  44. package/dist/core/extensions/index.d.ts +1 -1
  45. package/dist/core/extensions/index.d.ts.map +1 -1
  46. package/dist/core/extensions/index.js.map +1 -1
  47. package/dist/core/extensions/loader.d.ts.map +1 -1
  48. package/dist/core/extensions/loader.js +13 -1
  49. package/dist/core/extensions/loader.js.map +1 -1
  50. package/dist/core/extensions/types.d.ts +6 -1
  51. package/dist/core/extensions/types.d.ts.map +1 -1
  52. package/dist/core/extensions/types.js.map +1 -1
  53. package/dist/core/keybindings.d.ts +51 -11
  54. package/dist/core/keybindings.d.ts.map +1 -1
  55. package/dist/core/keybindings.js +11 -3
  56. package/dist/core/keybindings.js.map +1 -1
  57. package/dist/core/mcp-servers.d.ts +34 -7
  58. package/dist/core/mcp-servers.d.ts.map +1 -1
  59. package/dist/core/mcp-servers.js +52 -11
  60. package/dist/core/mcp-servers.js.map +1 -1
  61. package/dist/core/model-registry.d.ts +3 -1
  62. package/dist/core/model-registry.d.ts.map +1 -1
  63. package/dist/core/model-registry.js +4 -0
  64. package/dist/core/model-registry.js.map +1 -1
  65. package/dist/core/model-runtime.d.ts +6 -0
  66. package/dist/core/model-runtime.d.ts.map +1 -1
  67. package/dist/core/model-runtime.js +22 -18
  68. package/dist/core/model-runtime.js.map +1 -1
  69. package/dist/core/radius.d.ts +2 -0
  70. package/dist/core/radius.d.ts.map +1 -1
  71. package/dist/core/radius.js +2 -0
  72. package/dist/core/radius.js.map +1 -1
  73. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  74. package/dist/core/remote-catalog-provider.js +4 -9
  75. package/dist/core/remote-catalog-provider.js.map +1 -1
  76. package/dist/core/sdk.d.ts.map +1 -1
  77. package/dist/core/sdk.js +1 -0
  78. package/dist/core/sdk.js.map +1 -1
  79. package/dist/core/settings-manager.d.ts +7 -5
  80. package/dist/core/settings-manager.d.ts.map +1 -1
  81. package/dist/core/settings-manager.js +2 -1
  82. package/dist/core/settings-manager.js.map +1 -1
  83. package/dist/core/system-prompt.js +1 -1
  84. package/dist/core/system-prompt.js.map +1 -1
  85. package/dist/core/tasks/format.d.ts +7 -0
  86. package/dist/core/tasks/format.d.ts.map +1 -0
  87. package/dist/core/tasks/format.js +14 -0
  88. package/dist/core/tasks/format.js.map +1 -0
  89. package/dist/core/tasks/history.d.ts.map +1 -1
  90. package/dist/core/tasks/history.js +0 -19
  91. package/dist/core/tasks/history.js.map +1 -1
  92. package/dist/core/tasks/output.d.ts +8 -0
  93. package/dist/core/tasks/output.d.ts.map +1 -1
  94. package/dist/core/tasks/output.js +19 -0
  95. package/dist/core/tasks/output.js.map +1 -1
  96. package/dist/core/tasks/presentation.d.ts +1 -3
  97. package/dist/core/tasks/presentation.d.ts.map +1 -1
  98. package/dist/core/tasks/presentation.js +79 -166
  99. package/dist/core/tasks/presentation.js.map +1 -1
  100. package/dist/core/tasks/runtime.d.ts +16 -9
  101. package/dist/core/tasks/runtime.d.ts.map +1 -1
  102. package/dist/core/tasks/runtime.js +89 -106
  103. package/dist/core/tasks/runtime.js.map +1 -1
  104. package/dist/core/tasks/session.d.ts.map +1 -1
  105. package/dist/core/tasks/session.js +13 -4
  106. package/dist/core/tasks/session.js.map +1 -1
  107. package/dist/core/tasks/store.d.ts +20 -6
  108. package/dist/core/tasks/store.d.ts.map +1 -1
  109. package/dist/core/tasks/store.js +45 -18
  110. package/dist/core/tasks/store.js.map +1 -1
  111. package/dist/core/tasks/types.d.ts +14 -54
  112. package/dist/core/tasks/types.d.ts.map +1 -1
  113. package/dist/core/tasks/types.js +1 -5
  114. package/dist/core/tasks/types.js.map +1 -1
  115. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  116. package/dist/core/tools/renderers/bash.js +11 -34
  117. package/dist/core/tools/renderers/bash.js.map +1 -1
  118. package/dist/core/tools/shell-execution.d.ts +0 -1
  119. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  120. package/dist/core/tools/shell-execution.js +3 -10
  121. package/dist/core/tools/shell-execution.js.map +1 -1
  122. package/dist/core/tools/shell-tool.d.ts.map +1 -1
  123. package/dist/core/tools/shell-tool.js +2 -4
  124. package/dist/core/tools/shell-tool.js.map +1 -1
  125. package/dist/core/tools/tasks/actions.d.ts +1 -1
  126. package/dist/core/tools/tasks/actions.d.ts.map +1 -1
  127. package/dist/core/tools/tasks/actions.js +26 -12
  128. package/dist/core/tools/tasks/actions.js.map +1 -1
  129. package/dist/core/tools/tasks/constants.d.ts +2 -6
  130. package/dist/core/tools/tasks/constants.d.ts.map +1 -1
  131. package/dist/core/tools/tasks/constants.js +2 -6
  132. package/dist/core/tools/tasks/constants.js.map +1 -1
  133. package/dist/core/tools/tasks/schema.d.ts +1 -1
  134. package/dist/core/tools/tasks/schema.d.ts.map +1 -1
  135. package/dist/core/tools/tasks/schema.js +8 -7
  136. package/dist/core/tools/tasks/schema.js.map +1 -1
  137. package/dist/core/tools/tasks/types.d.ts +8 -38
  138. package/dist/core/tools/tasks/types.d.ts.map +1 -1
  139. package/dist/core/tools/tasks/types.js +6 -11
  140. package/dist/core/tools/tasks/types.js.map +1 -1
  141. package/dist/core/virtual-models.d.ts +2 -0
  142. package/dist/core/virtual-models.d.ts.map +1 -1
  143. package/dist/core/virtual-models.js +19 -12
  144. package/dist/core/virtual-models.js.map +1 -1
  145. package/dist/extensions/btw/agent.d.ts +17 -8
  146. package/dist/extensions/btw/agent.d.ts.map +1 -1
  147. package/dist/extensions/btw/agent.js +74 -76
  148. package/dist/extensions/btw/agent.js.map +1 -1
  149. package/dist/extensions/btw/controller.d.ts +0 -1
  150. package/dist/extensions/btw/controller.d.ts.map +1 -1
  151. package/dist/extensions/btw/controller.js +26 -33
  152. package/dist/extensions/btw/controller.js.map +1 -1
  153. package/dist/extensions/btw/panel.d.ts +13 -14
  154. package/dist/extensions/btw/panel.d.ts.map +1 -1
  155. package/dist/extensions/btw/panel.js +38 -29
  156. package/dist/extensions/btw/panel.js.map +1 -1
  157. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  158. package/dist/extensions/codemode/execute.js +197 -51
  159. package/dist/extensions/codemode/execute.js.map +1 -1
  160. package/dist/extensions/codemode/renderer.d.ts.map +1 -1
  161. package/dist/extensions/codemode/renderer.js +43 -25
  162. package/dist/extensions/codemode/renderer.js.map +1 -1
  163. package/dist/extensions/codemode/tool.d.ts +12 -11
  164. package/dist/extensions/codemode/tool.d.ts.map +1 -1
  165. package/dist/extensions/codemode/tool.js +73 -129
  166. package/dist/extensions/codemode/tool.js.map +1 -1
  167. package/dist/extensions/codemode/worker.d.ts +1 -1
  168. package/dist/extensions/codemode/worker.js +1 -1
  169. package/dist/extensions/codemode/worker.js.map +1 -1
  170. package/dist/extensions/deepwiki/schema.d.ts +1 -1
  171. package/dist/extensions/index.d.ts.map +1 -1
  172. package/dist/extensions/index.js +0 -2
  173. package/dist/extensions/index.js.map +1 -1
  174. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  175. package/dist/extensions/mcp/cli.js +20 -5
  176. package/dist/extensions/mcp/cli.js.map +1 -1
  177. package/dist/extensions/mcp/config.d.ts +4 -2
  178. package/dist/extensions/mcp/config.d.ts.map +1 -1
  179. package/dist/extensions/mcp/config.js +14 -2
  180. package/dist/extensions/mcp/config.js.map +1 -1
  181. package/dist/extensions/mcp/index.d.ts +32 -8
  182. package/dist/extensions/mcp/index.d.ts.map +1 -1
  183. package/dist/extensions/mcp/index.js +235 -61
  184. package/dist/extensions/mcp/index.js.map +1 -1
  185. package/dist/extensions/mcp/oauth.d.ts +10 -7
  186. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  187. package/dist/extensions/mcp/oauth.js +56 -31
  188. package/dist/extensions/mcp/oauth.js.map +1 -1
  189. package/dist/extensions/mcp/runtime.d.ts +2 -0
  190. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  191. package/dist/extensions/mcp/runtime.js +16 -9
  192. package/dist/extensions/mcp/runtime.js.map +1 -1
  193. package/dist/extensions/mcp/tools.d.ts +5 -4
  194. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  195. package/dist/extensions/mcp/tools.js +33 -15
  196. package/dist/extensions/mcp/tools.js.map +1 -1
  197. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  198. package/dist/extensions/mcp/ui.js +4 -2
  199. package/dist/extensions/mcp/ui.js.map +1 -1
  200. package/dist/extensions/provider/catalog.d.ts +8 -1
  201. package/dist/extensions/provider/catalog.d.ts.map +1 -1
  202. package/dist/extensions/provider/catalog.js +31 -0
  203. package/dist/extensions/provider/catalog.js.map +1 -1
  204. package/dist/extensions/provider/connection.d.ts +4 -1
  205. package/dist/extensions/provider/connection.d.ts.map +1 -1
  206. package/dist/extensions/provider/connection.js +1 -5
  207. package/dist/extensions/provider/connection.js.map +1 -1
  208. package/dist/extensions/provider/editing.d.ts +36 -0
  209. package/dist/extensions/provider/editing.d.ts.map +1 -0
  210. package/dist/extensions/provider/editing.js +99 -0
  211. package/dist/extensions/provider/editing.js.map +1 -0
  212. package/dist/extensions/provider/json-fields.d.ts +5 -0
  213. package/dist/extensions/provider/json-fields.d.ts.map +1 -0
  214. package/dist/extensions/provider/json-fields.js +31 -0
  215. package/dist/extensions/provider/json-fields.js.map +1 -0
  216. package/dist/extensions/provider/store.d.ts +1 -4
  217. package/dist/extensions/provider/store.d.ts.map +1 -1
  218. package/dist/extensions/provider/store.js +2 -30
  219. package/dist/extensions/provider/store.js.map +1 -1
  220. package/dist/extensions/provider/ui/builtin-data.d.ts +0 -2
  221. package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -1
  222. package/dist/extensions/provider/ui/builtin-data.js +14 -79
  223. package/dist/extensions/provider/ui/builtin-data.js.map +1 -1
  224. package/dist/extensions/provider/ui/compat.d.ts.map +1 -1
  225. package/dist/extensions/provider/ui/compat.js +6 -9
  226. package/dist/extensions/provider/ui/compat.js.map +1 -1
  227. package/dist/extensions/provider/ui/controls.d.ts +48 -0
  228. package/dist/extensions/provider/ui/controls.d.ts.map +1 -0
  229. package/dist/extensions/provider/ui/controls.js +108 -0
  230. package/dist/extensions/provider/ui/controls.js.map +1 -0
  231. package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -1
  232. package/dist/extensions/provider/ui/dictionary.js +6 -9
  233. package/dist/extensions/provider/ui/dictionary.js.map +1 -1
  234. package/dist/extensions/provider/ui/editor.d.ts +3 -3
  235. package/dist/extensions/provider/ui/editor.d.ts.map +1 -1
  236. package/dist/extensions/provider/ui/editor.js +35 -140
  237. package/dist/extensions/provider/ui/editor.js.map +1 -1
  238. package/dist/extensions/provider/ui/fetch-models.d.ts +0 -3
  239. package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -1
  240. package/dist/extensions/provider/ui/fetch-models.js +49 -44
  241. package/dist/extensions/provider/ui/fetch-models.js.map +1 -1
  242. package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -1
  243. package/dist/extensions/provider/ui/model-fields.js +11 -14
  244. package/dist/extensions/provider/ui/model-fields.js.map +1 -1
  245. package/dist/extensions/provider/ui/model-options.d.ts +4 -34
  246. package/dist/extensions/provider/ui/model-options.d.ts.map +1 -1
  247. package/dist/extensions/provider/ui/model-options.js +90 -271
  248. package/dist/extensions/provider/ui/model-options.js.map +1 -1
  249. package/dist/extensions/provider/ui/pane.d.ts +4 -18
  250. package/dist/extensions/provider/ui/pane.d.ts.map +1 -1
  251. package/dist/extensions/provider/ui/pane.js.map +1 -1
  252. package/dist/extensions/provider/ui/provider-fields.d.ts +2 -19
  253. package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -1
  254. package/dist/extensions/provider/ui/provider-fields.js +58 -171
  255. package/dist/extensions/provider/ui/provider-fields.js.map +1 -1
  256. package/dist/extensions/provider/ui/thinking-map.d.ts.map +1 -1
  257. package/dist/extensions/provider/ui/thinking-map.js +6 -9
  258. package/dist/extensions/provider/ui/thinking-map.js.map +1 -1
  259. package/dist/extensions/question/detail-pane.d.ts +7 -2
  260. package/dist/extensions/question/detail-pane.d.ts.map +1 -1
  261. package/dist/extensions/question/detail-pane.js +16 -5
  262. package/dist/extensions/question/detail-pane.js.map +1 -1
  263. package/dist/extensions/question/dialog-view.d.ts +41 -0
  264. package/dist/extensions/question/dialog-view.d.ts.map +1 -0
  265. package/dist/extensions/question/dialog-view.js +329 -0
  266. package/dist/extensions/question/dialog-view.js.map +1 -0
  267. package/dist/extensions/question/dialog.d.ts +3 -3
  268. package/dist/extensions/question/dialog.d.ts.map +1 -1
  269. package/dist/extensions/question/dialog.js +220 -634
  270. package/dist/extensions/question/dialog.js.map +1 -1
  271. package/dist/extensions/question/index.d.ts.map +1 -1
  272. package/dist/extensions/question/index.js +2 -3
  273. package/dist/extensions/question/index.js.map +1 -1
  274. package/dist/extensions/question/render.d.ts.map +1 -1
  275. package/dist/extensions/question/render.js +22 -32
  276. package/dist/extensions/question/render.js.map +1 -1
  277. package/dist/extensions/question/results.d.ts.map +1 -1
  278. package/dist/extensions/question/results.js +56 -12
  279. package/dist/extensions/question/results.js.map +1 -1
  280. package/dist/extensions/question/schema.d.ts +21 -6
  281. package/dist/extensions/question/schema.d.ts.map +1 -1
  282. package/dist/extensions/question/schema.js.map +1 -1
  283. package/dist/extensions/question/state.d.ts +9 -7
  284. package/dist/extensions/question/state.d.ts.map +1 -1
  285. package/dist/extensions/question/state.js +63 -54
  286. package/dist/extensions/question/state.js.map +1 -1
  287. package/dist/extensions/question/types.d.ts +41 -18
  288. package/dist/extensions/question/types.d.ts.map +1 -1
  289. package/dist/extensions/question/types.js.map +1 -1
  290. package/dist/extensions/statusline/index.d.ts +4 -0
  291. package/dist/extensions/statusline/index.d.ts.map +1 -1
  292. package/dist/extensions/statusline/index.js +18 -355
  293. package/dist/extensions/statusline/index.js.map +1 -1
  294. package/dist/extensions/statusline/render.d.ts +19 -0
  295. package/dist/extensions/statusline/render.d.ts.map +1 -0
  296. package/dist/extensions/statusline/render.js +163 -0
  297. package/dist/extensions/statusline/render.js.map +1 -0
  298. package/dist/extensions/statusline/usage.d.ts +12 -0
  299. package/dist/extensions/statusline/usage.d.ts.map +1 -0
  300. package/dist/extensions/statusline/usage.js +36 -0
  301. package/dist/extensions/statusline/usage.js.map +1 -0
  302. package/dist/extensions/tool-search/tool.d.ts +5 -4
  303. package/dist/extensions/tool-search/tool.d.ts.map +1 -1
  304. package/dist/extensions/tool-search/tool.js +7 -28
  305. package/dist/extensions/tool-search/tool.js.map +1 -1
  306. package/dist/extensions/web-search/execute.d.ts +1 -5
  307. package/dist/extensions/web-search/execute.d.ts.map +1 -1
  308. package/dist/extensions/web-search/execute.js +4 -9
  309. package/dist/extensions/web-search/execute.js.map +1 -1
  310. package/dist/extensions/web-search/format.d.ts +12 -3
  311. package/dist/extensions/web-search/format.d.ts.map +1 -1
  312. package/dist/extensions/web-search/format.js +33 -25
  313. package/dist/extensions/web-search/format.js.map +1 -1
  314. package/dist/extensions/web-search/index.d.ts.map +1 -1
  315. package/dist/extensions/web-search/index.js +6 -29
  316. package/dist/extensions/web-search/index.js.map +1 -1
  317. package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -1
  318. package/dist/extensions/web-search/providers/deepseek.js +0 -1
  319. package/dist/extensions/web-search/providers/deepseek.js.map +1 -1
  320. package/dist/extensions/web-search/providers/http.d.ts +1 -1
  321. package/dist/extensions/web-search/providers/http.d.ts.map +1 -1
  322. package/dist/extensions/web-search/providers/http.js.map +1 -1
  323. package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -1
  324. package/dist/extensions/web-search/providers/minimax.js +0 -1
  325. package/dist/extensions/web-search/providers/minimax.js.map +1 -1
  326. package/dist/extensions/web-search/render.d.ts +7 -2
  327. package/dist/extensions/web-search/render.d.ts.map +1 -1
  328. package/dist/extensions/web-search/render.js +32 -4
  329. package/dist/extensions/web-search/render.js.map +1 -1
  330. package/dist/extensions/web-search/results.d.ts.map +1 -1
  331. package/dist/extensions/web-search/results.js +3 -8
  332. package/dist/extensions/web-search/results.js.map +1 -1
  333. package/dist/extensions/web-search/types.d.ts +4 -2
  334. package/dist/extensions/web-search/types.d.ts.map +1 -1
  335. package/dist/extensions/web-search/types.js.map +1 -1
  336. package/dist/index.d.ts +2 -4
  337. package/dist/index.d.ts.map +1 -1
  338. package/dist/index.js +0 -1
  339. package/dist/index.js.map +1 -1
  340. package/dist/main.d.ts.map +1 -1
  341. package/dist/main.js +6 -0
  342. package/dist/main.js.map +1 -1
  343. package/dist/modes/interactive/components/oauth-selector.d.ts +8 -2
  344. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  345. package/dist/modes/interactive/components/oauth-selector.js +24 -21
  346. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  347. package/dist/modes/interactive/components/pi-logo-animation.d.ts +68 -0
  348. package/dist/modes/interactive/components/pi-logo-animation.d.ts.map +1 -0
  349. package/dist/modes/interactive/components/pi-logo-animation.js +1076 -0
  350. package/dist/modes/interactive/components/pi-logo-animation.js.map +1 -0
  351. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts +7 -0
  352. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts.map +1 -0
  353. package/dist/modes/interactive/components/pi-logo-animation.lazy.js +12 -0
  354. package/dist/modes/interactive/components/pi-logo-animation.lazy.js.map +1 -0
  355. package/dist/modes/interactive/components/pi-logo.d.ts +7 -0
  356. package/dist/modes/interactive/components/pi-logo.d.ts.map +1 -1
  357. package/dist/modes/interactive/components/pi-logo.js +13 -1
  358. package/dist/modes/interactive/components/pi-logo.js.map +1 -1
  359. package/dist/modes/interactive/components/radius-login-selector.d.ts +15 -0
  360. package/dist/modes/interactive/components/radius-login-selector.d.ts.map +1 -0
  361. package/dist/modes/interactive/components/radius-login-selector.js +83 -0
  362. package/dist/modes/interactive/components/radius-login-selector.js.map +1 -0
  363. package/dist/modes/interactive/components/settings-selector.d.ts +3 -3
  364. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  365. package/dist/modes/interactive/components/settings-selector.js +5 -5
  366. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  367. package/dist/modes/interactive/components/status-marker.d.ts +0 -2
  368. package/dist/modes/interactive/components/status-marker.d.ts.map +1 -1
  369. package/dist/modes/interactive/components/status-marker.js +0 -4
  370. package/dist/modes/interactive/components/status-marker.js.map +1 -1
  371. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  372. package/dist/modes/interactive/components/user-message.js +5 -4
  373. package/dist/modes/interactive/components/user-message.js.map +1 -1
  374. package/dist/modes/interactive/components/visual-truncate.d.ts +27 -3
  375. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  376. package/dist/modes/interactive/components/visual-truncate.js +36 -6
  377. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  378. package/dist/modes/interactive/interactive-mode.d.ts +10 -0
  379. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  380. package/dist/modes/interactive/interactive-mode.js +167 -40
  381. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  382. package/dist/modes/interactive/tasks/completion-render.d.ts +1 -1
  383. package/dist/modes/interactive/tasks/completion-render.d.ts.map +1 -1
  384. package/dist/modes/interactive/tasks/completion-render.js +28 -82
  385. package/dist/modes/interactive/tasks/completion-render.js.map +1 -1
  386. package/dist/modes/interactive/tasks/index.d.ts.map +1 -1
  387. package/dist/modes/interactive/tasks/index.js +18 -13
  388. package/dist/modes/interactive/tasks/index.js.map +1 -1
  389. package/dist/modes/interactive/tasks/inspector.d.ts +35 -0
  390. package/dist/modes/interactive/tasks/inspector.d.ts.map +1 -0
  391. package/dist/modes/interactive/tasks/inspector.js +93 -0
  392. package/dist/modes/interactive/tasks/inspector.js.map +1 -0
  393. package/dist/modes/interactive/tasks/layout.d.ts +40 -0
  394. package/dist/modes/interactive/tasks/layout.d.ts.map +1 -0
  395. package/dist/modes/interactive/tasks/layout.js +212 -0
  396. package/dist/modes/interactive/tasks/layout.js.map +1 -0
  397. package/dist/modes/interactive/tasks/list.d.ts +21 -0
  398. package/dist/modes/interactive/tasks/list.d.ts.map +1 -0
  399. package/dist/modes/interactive/tasks/list.js +96 -0
  400. package/dist/modes/interactive/tasks/list.js.map +1 -0
  401. package/dist/modes/interactive/tasks/manager.d.ts +29 -36
  402. package/dist/modes/interactive/tasks/manager.d.ts.map +1 -1
  403. package/dist/modes/interactive/tasks/manager.js +315 -315
  404. package/dist/modes/interactive/tasks/manager.js.map +1 -1
  405. package/dist/modes/interactive/tasks/model.d.ts +21 -0
  406. package/dist/modes/interactive/tasks/model.d.ts.map +1 -0
  407. package/dist/modes/interactive/tasks/model.js +41 -0
  408. package/dist/modes/interactive/tasks/model.js.map +1 -0
  409. package/dist/modes/interactive/tasks/render.d.ts +5 -19
  410. package/dist/modes/interactive/tasks/render.d.ts.map +1 -1
  411. package/dist/modes/interactive/tasks/render.js +18 -102
  412. package/dist/modes/interactive/tasks/render.js.map +1 -1
  413. package/dist/modes/interactive/tasks/task-view.d.ts +8 -48
  414. package/dist/modes/interactive/tasks/task-view.d.ts.map +1 -1
  415. package/dist/modes/interactive/tasks/task-view.js +11 -51
  416. package/dist/modes/interactive/tasks/task-view.js.map +1 -1
  417. package/dist/modes/interactive/tasks/text.d.ts +1 -7
  418. package/dist/modes/interactive/tasks/text.d.ts.map +1 -1
  419. package/dist/modes/interactive/tasks/text.js +6 -29
  420. package/dist/modes/interactive/tasks/text.js.map +1 -1
  421. package/dist/modes/interactive/tasks/viewport.d.ts +2 -0
  422. package/dist/modes/interactive/tasks/viewport.d.ts.map +1 -1
  423. package/dist/modes/interactive/tasks/viewport.js +17 -7
  424. package/dist/modes/interactive/tasks/viewport.js.map +1 -1
  425. package/dist/modes/interactive/tasks/views.d.ts +9 -0
  426. package/dist/modes/interactive/tasks/views.d.ts.map +1 -0
  427. package/dist/modes/interactive/tasks/views.js +42 -0
  428. package/dist/modes/interactive/tasks/views.js.map +1 -0
  429. package/dist/modes/interactive/theme/system-theme.d.ts +2 -1
  430. package/dist/modes/interactive/theme/system-theme.d.ts.map +1 -1
  431. package/dist/modes/interactive/theme/system-theme.js +16 -5
  432. package/dist/modes/interactive/theme/system-theme.js.map +1 -1
  433. package/docs/bundled/README.md +0 -1
  434. package/docs/bundled/extensions/question.md +2 -2
  435. package/docs/bundled/extensions/statusline.md +24 -15
  436. package/docs/bundled/tasks.md +34 -48
  437. package/docs/codemode.md +207 -0
  438. package/docs/docs.json +4 -4
  439. package/docs/environment-variables.md +1 -1
  440. package/docs/extensions.md +7 -9
  441. package/docs/keybindings.md +13 -6
  442. package/docs/mcp.md +142 -82
  443. package/docs/models.md +21 -0
  444. package/docs/providers.md +8 -0
  445. package/docs/sdk.md +2 -2
  446. package/docs/session-format.md +12 -22
  447. package/docs/settings.md +4 -2
  448. package/docs/tui.md +1 -1
  449. package/docs/usage.md +6 -16
  450. package/examples/README.md +1 -1
  451. package/examples/extensions/README.md +1 -2
  452. package/examples/extensions/built-in-tool-renderer.ts +16 -40
  453. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  454. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  455. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  456. package/examples/extensions/gondolin/package-lock.json +2 -2
  457. package/examples/extensions/gondolin/package.json +1 -1
  458. package/examples/extensions/minimal-mode.ts +24 -96
  459. package/examples/extensions/sandbox/package-lock.json +2 -2
  460. package/examples/extensions/sandbox/package.json +1 -1
  461. package/examples/extensions/with-deps/package-lock.json +2 -2
  462. package/examples/extensions/with-deps/package.json +1 -1
  463. package/examples/sdk/14-codemode-mcp.ts +2 -2
  464. package/npm-shrinkwrap.json +69 -58
  465. package/package.json +11 -10
  466. package/dist/bundle/chunks/anthropic-messages-KIXY7EDT.js +0 -6
  467. package/dist/bundle/chunks/chunk-CLLDCO64.js +0 -2
  468. package/dist/bundle/chunks/chunk-UUSTLCVC.js +0 -2
  469. package/dist/bundle/chunks/chunk-VWCWZD6E.js +0 -2
  470. package/dist/bundle/chunks/virtual-modules-TQRORWJQ.js +0 -2
  471. package/dist/core/tasks/view-data.d.ts +0 -8
  472. package/dist/core/tasks/view-data.d.ts.map +0 -1
  473. package/dist/core/tasks/view-data.js +0 -61
  474. package/dist/core/tasks/view-data.js.map +0 -1
  475. package/dist/core/tasks/view.d.ts +0 -30
  476. package/dist/core/tasks/view.d.ts.map +0 -1
  477. package/dist/core/tasks/view.js +0 -50
  478. package/dist/core/tasks/view.js.map +0 -1
  479. package/dist/core/tools/renderers/shell-task.d.ts +0 -4
  480. package/dist/core/tools/renderers/shell-task.d.ts.map +0 -1
  481. package/dist/core/tools/renderers/shell-task.js +0 -35
  482. package/dist/core/tools/renderers/shell-task.js.map +0 -1
  483. package/dist/extensions/todo/constants.d.ts +0 -16
  484. package/dist/extensions/todo/constants.d.ts.map +0 -1
  485. package/dist/extensions/todo/constants.js +0 -30
  486. package/dist/extensions/todo/constants.js.map +0 -1
  487. package/dist/extensions/todo/index.d.ts +0 -3
  488. package/dist/extensions/todo/index.d.ts.map +0 -1
  489. package/dist/extensions/todo/index.js +0 -98
  490. package/dist/extensions/todo/index.js.map +0 -1
  491. package/dist/extensions/todo/schema.d.ts +0 -60
  492. package/dist/extensions/todo/schema.d.ts.map +0 -1
  493. package/dist/extensions/todo/schema.js +0 -56
  494. package/dist/extensions/todo/schema.js.map +0 -1
  495. package/dist/extensions/todo/state.d.ts +0 -34
  496. package/dist/extensions/todo/state.d.ts.map +0 -1
  497. package/dist/extensions/todo/state.js +0 -425
  498. package/dist/extensions/todo/state.js.map +0 -1
  499. package/dist/extensions/todo/view.d.ts +0 -36
  500. package/dist/extensions/todo/view.d.ts.map +0 -1
  501. package/dist/extensions/todo/view.js +0 -580
  502. package/dist/extensions/todo/view.js.map +0 -1
  503. package/dist/extensions/todo/widget.d.ts +0 -15
  504. package/dist/extensions/todo/widget.d.ts.map +0 -1
  505. package/dist/extensions/todo/widget.js +0 -50
  506. package/dist/extensions/todo/widget.js.map +0 -1
  507. package/dist/modes/interactive/tasks/fallback-view.d.ts +0 -4
  508. package/dist/modes/interactive/tasks/fallback-view.d.ts.map +0 -1
  509. package/dist/modes/interactive/tasks/fallback-view.js +0 -27
  510. package/dist/modes/interactive/tasks/fallback-view.js.map +0 -1
  511. package/docs/bundled/extensions/todo.md +0 -104
  512. package/examples/extensions/todo.ts +0 -297
@@ -1,74 +1,21 @@
1
1
  import "./keybindings.js";
2
- import { CURSOR_MARKER, Editor, Markdown, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
3
- import { keyLabel as configuredKeyLabel } from "../../modes/interactive/components/keybinding-hints.js";
4
- import { getMarkdownTheme } from "../../modes/interactive/theme/theme.js";
5
- import { DetailPane } from "./detail-pane.js";
6
- import { ruleBorder, wrapWithPrefix } from "./dialog-primitives.js";
2
+ import { Editor } from "@earendil-works/pi-tui";
3
+ import { keyLabel } from "../../modes/interactive/components/keybinding-hints.js";
4
+ import { QuestionDialogView } from "./dialog-view.js";
7
5
  import { QUESTION_LIMITS } from "./limits.js";
8
- import { moreOptionsHint, windowItems } from "./option-window.js";
9
- import { PreviewLinesCache, WidthCachedRender } from "./render-cache.js";
10
- import { displayOptions, firstUnanswered, hasAnswer, hasMultiAnswer, newQuestionState, orderedAnswers, } from "./state.js";
11
- /**
12
- * The dialog replaces the editor in the bottom dock, and the dock grows with its
13
- * content. Capping the dialog at half the terminal keeps the transcript (often the
14
- * assistant reply the question refers to) readable while the dialog is open.
15
- */
16
- const DIALOG_MAX_FRACTION = 0.5;
17
- const DIALOG_MIN_ROWS = 16;
18
- const VIEWPORT_SCROLL_STEP = 4;
19
- function dialogRowBudget(terminalRows) {
20
- const rows = Math.max(1, Math.floor(terminalRows));
21
- return Math.min(rows, Math.max(DIALOG_MIN_ROWS, Math.floor(rows * DIALOG_MAX_FRACTION)));
22
- }
23
- /** Clamp `lines` to `maxRows` between the first and last line, scrolling around the anchor. */
24
- function applyVerticalViewport(lines, width, maxRows, theme, options = {}) {
25
- if (lines.length <= maxRows)
26
- return { lines, start: 0, maxStart: 0 };
27
- if (maxRows < 5) {
28
- const anchor = Math.max(0, Math.min(lines.length - 1, options.anchorLine ?? 0));
29
- const start = Math.max(0, Math.min(lines.length - maxRows, anchor - Math.floor(maxRows / 2)));
30
- return { lines: lines.slice(start, start + maxRows), start, maxStart: lines.length - maxRows };
31
- }
32
- const first = lines[0] ?? "";
33
- const last = lines[lines.length - 1] ?? "";
34
- const body = lines.slice(1, -1);
35
- const contentRows = maxRows - 4;
36
- const maxStart = Math.max(0, body.length - contentRows);
37
- const anchorInBody = Math.max(0, Math.min(body.length - 1, (options.anchorLine ?? 1) - 1));
38
- const requestedStart = options.preferredStart ?? Math.max(0, anchorInBody - Math.floor(contentRows / 2));
39
- const start = Math.max(0, Math.min(maxStart, requestedStart));
40
- const bottomHidden = Math.max(0, body.length - start - contentRows);
41
- const indicator = (text) => theme.fg("dim", truncateToWidth(text, Math.max(1, width)));
42
- return {
43
- lines: [
44
- first,
45
- start > 0 ? indicator(`↑ ${start} rows hidden`) : "",
46
- ...body.slice(start, start + contentRows),
47
- bottomHidden > 0 ? indicator(`↓ ${bottomHidden} rows hidden`) : "",
48
- last,
49
- ],
50
- start,
51
- maxStart,
52
- };
53
- }
6
+ import { WidthCachedRender } from "./render-cache.js";
7
+ import { customText, displayOptions, firstUnanswered, hasAnswer, newQuestionState, orderedAnswers } from "./state.js";
54
8
  export function createQuestionDialog(questions, signal) {
55
9
  return (tui, theme, keybindings, done) => {
56
- let currentIdx = 0;
57
- let view = "question";
58
- let inputMode;
59
- let noteTarget;
60
- let notesSnapshot;
61
- let reviewScrollOffset = 0;
62
- let reviewMaxScrollOffset = 0;
10
+ const states = questions.map(newQuestionState);
11
+ const optionsByQuestion = questions.map(displayOptions);
12
+ let current = 0;
13
+ let mode = { kind: "choose" };
63
14
  let dialogFocused = false;
64
- let footerFocused = false;
65
15
  let finished = false;
66
16
  const cache = new WidthCachedRender();
67
- const previewCache = new PreviewLinesCache();
68
- const details = new DetailPane();
69
- const states = questions.map(() => newQuestionState());
70
- const optionsByQuestion = questions.map((question) => displayOptions(question));
71
- const editorTheme = {
17
+ const view = new QuestionDialogView(theme, keybindings);
18
+ const editor = new Editor(tui, {
72
19
  borderColor: (s) => theme.fg("accent", s),
73
20
  selectList: {
74
21
  selectedPrefix: (t) => theme.fg("accent", t),
@@ -77,640 +24,272 @@ export function createQuestionDialog(questions, signal) {
77
24
  scrollInfo: (t) => theme.fg("dim", t),
78
25
  noMatch: (t) => theme.fg("warning", t),
79
26
  },
80
- };
81
- const editor = new Editor(tui, editorTheme);
82
- const currentQuestion = () => questions[currentIdx];
83
- const currentState = () => states[currentIdx];
84
- const currentOptions = () => optionsByQuestion[currentIdx];
27
+ });
85
28
  const keyMatches = (data, keybinding) => keybindings.matches(data, keybinding);
86
- const keyLabel = (keybinding) => configuredKeyLabel(keybinding, { keybindings });
87
- const keyAction = (keybinding, action) => {
88
- const label = keyLabel(keybinding);
89
- return label ? `${label} ${action}` : "";
90
- };
91
- // Editor reads the global TUI manager internally, while dialog controls use the injected manager.
92
- const editorSubmitAction = (action) => {
93
- const label = configuredKeyLabel("tui.input.submit");
94
- return label ? `${label} ${action}` : "";
95
- };
96
- const keyGroupAction = (bindings, action) => {
97
- const labels = bindings.map(keyLabel).filter(Boolean).join("/");
98
- return labels ? `${labels} ${action}` : "";
99
- };
100
- const joinHints = (...hints) => hints.filter(Boolean).join(" • ");
101
- function finish(result) {
102
- if (finished)
103
- return;
104
- finished = true;
105
- done(result);
106
- }
107
- const onAbort = () => finish({ answers: orderedAnswers(questions, states), outcome: "cancelled" });
108
- if (signal?.aborted)
109
- onAbort();
110
- else
111
- signal?.addEventListener("abort", onAbort, { once: true });
29
+ const state = () => states[current];
30
+ const options = () => optionsByQuestion[current];
31
+ const isEditing = () => mode.kind === "custom" || mode.kind === "notes";
32
+ const snapshot = () => ({ questions, states, current, options: options(), mode, editor });
112
33
  function refresh() {
113
34
  cache.invalidate();
114
35
  tui.requestRender();
115
36
  }
116
- function syncEditorFocus() {
117
- editor.focused = dialogFocused && inputMode !== undefined;
118
- }
119
- function setCurrentIdx(index) {
120
- details.reset();
121
- currentIdx = Math.max(0, Math.min(questions.length - 1, index));
122
- view = "question";
123
- inputMode = undefined;
124
- noteTarget = undefined;
125
- reviewScrollOffset = 0;
126
- reviewMaxScrollOffset = 0;
127
- notesSnapshot = undefined;
128
- footerFocused = false;
129
- editor.setText("");
130
- syncEditorFocus();
37
+ /** The one place the mode changes: scrolling restarts, and the editor gets its text and focus. */
38
+ function setMode(next, editorText = "") {
39
+ mode = next;
40
+ view.resetScroll();
41
+ editor.setText(editorText);
42
+ editor.focused = dialogFocused && isEditing();
131
43
  refresh();
132
44
  }
133
- function clearWarning() {
134
- currentState().warning = undefined;
45
+ function finish(outcome) {
46
+ if (finished)
47
+ return;
48
+ finished = true;
49
+ done({ outcome, answers: orderedAnswers(questions, states) });
135
50
  }
136
- function focusedOption() {
137
- return currentOptions()[currentState().optionIndex];
51
+ /** Leave an editor without saving; notes on a single-select question undo their tentative selection. */
52
+ function discardEdit() {
53
+ const draft = state().draft;
54
+ if (mode.kind === "notes" && draft.kind === "single")
55
+ draft.choice = mode.previous;
138
56
  }
139
- function showReview() {
140
- const missing = firstUnanswered(states);
141
- if (missing !== undefined) {
142
- setCurrentIdx(missing);
143
- currentState().warning = "Answer this question before reviewing your answers.";
144
- refresh();
145
- return;
146
- }
147
- view = "review";
148
- inputMode = undefined;
149
- noteTarget = undefined;
150
- reviewScrollOffset = 0;
151
- reviewMaxScrollOffset = 0;
152
- footerFocused = false;
153
- editor.setText("");
154
- syncEditorFocus();
155
- refresh();
57
+ const onAbort = () => {
58
+ discardEdit();
59
+ finish("cancelled");
60
+ };
61
+ if (signal?.aborted)
62
+ onAbort();
63
+ else
64
+ signal?.addEventListener("abort", onAbort, { once: true });
65
+ function goTo(index, warning) {
66
+ current = Math.max(0, Math.min(questions.length - 1, index));
67
+ if (warning)
68
+ state().warning = warning;
69
+ setMode({ kind: "choose" });
156
70
  }
157
- function advanceAfterAnswer() {
158
- if (questions.length === 1 && !currentQuestion().multiSelect) {
159
- finish({ answers: orderedAnswers(questions, states), outcome: "answered" });
71
+ /** After an answer: finish a lone single-select question, else review or move to the next unanswered one. */
72
+ function advance() {
73
+ if (questions.length === 1 && state().draft.kind === "single") {
74
+ finish("answered");
160
75
  return;
161
76
  }
162
77
  const missing = firstUnanswered(states);
163
- if (missing === undefined) {
164
- showReview();
165
- return;
166
- }
167
- setCurrentIdx(missing);
168
- }
169
- function beginCustomInput() {
170
- details.reset();
171
- const state = currentState();
172
- inputMode = "custom";
173
- noteTarget = undefined;
174
- const existingSingle = state.singleAnswer?.kind === "custom" ? state.singleAnswer.answer : undefined;
175
- editor.setText(state.customAnswer?.text ?? existingSingle ?? "");
176
- syncEditorFocus();
177
- refresh();
78
+ if (missing === undefined)
79
+ setMode({ kind: "review" });
80
+ else
81
+ goTo(missing);
178
82
  }
179
- function selectSingleOption(option) {
180
- const question = currentQuestion();
181
- const state = currentState();
182
- state.singleAnswer = {
183
- questionIndex: currentIdx,
184
- question: question.question,
185
- header: question.header,
186
- kind: "option",
187
- answer: option.label,
188
- ...(option.preview ? { preview: option.preview } : {}),
189
- };
190
- state.warning = undefined;
83
+ function editCustom() {
84
+ setMode({ kind: "custom" }, customText(state()) ?? "");
191
85
  }
192
- function beginNotesInput() {
193
- details.reset();
194
- const question = currentQuestion();
195
- const state = currentState();
196
- const option = focusedOption();
197
- if (!option || option.kind === "other") {
198
- beginCustomInput();
86
+ function editNotes(option) {
87
+ const target = state();
88
+ const draft = target.draft;
89
+ if (option.kind === "other") {
90
+ editCustom();
199
91
  return;
200
92
  }
201
- if (question.multiSelect && !state.multiSelected.has(option.optionIndex)) {
202
- state.warning = "Select the option first, then add notes.";
93
+ if (draft.kind === "multi" && !draft.selected.has(option.optionIndex)) {
94
+ target.warning = "Select the option first, then add notes.";
203
95
  refresh();
204
96
  return;
205
97
  }
206
- if (!question.multiSelect) {
207
- notesSnapshot = { prior: state.singleAnswer };
208
- selectSingleOption(option);
98
+ const previous = draft.kind === "single" ? draft.choice : undefined;
99
+ if (draft.kind === "single") {
100
+ draft.choice = { kind: "option", index: option.optionIndex };
101
+ target.warning = undefined;
209
102
  }
210
- inputMode = "notes";
211
- noteTarget = option.label;
212
- editor.setText(state.notesByOption.get(option.label) ?? "");
213
- syncEditorFocus();
214
- refresh();
103
+ setMode({ kind: "notes", option: option.optionIndex, previous }, target.notes.get(option.optionIndex) ?? "");
215
104
  }
216
- function recordSingle(option) {
105
+ function pickSingle(option) {
106
+ const target = state();
107
+ if (target.draft.kind !== "single")
108
+ return;
217
109
  if (option.kind === "other") {
218
- if (currentState().singleAnswer?.kind === "custom") {
219
- advanceAfterAnswer();
220
- return;
221
- }
222
- beginCustomInput();
110
+ if (target.draft.choice?.kind === "custom")
111
+ advance();
112
+ else
113
+ editCustom();
223
114
  return;
224
115
  }
225
- selectSingleOption(option);
226
- advanceAfterAnswer();
116
+ target.draft.choice = { kind: "option", index: option.optionIndex };
117
+ target.warning = undefined;
118
+ advance();
227
119
  }
228
- function recordMulti() {
229
- const state = currentState();
230
- if (!hasMultiAnswer(state)) {
231
- state.warning = "Select at least one option, type a custom answer, or cancel the questions.";
232
- refresh();
120
+ function toggleMulti(option) {
121
+ const target = state();
122
+ const draft = target.draft;
123
+ if (draft.kind !== "multi")
233
124
  return;
125
+ target.warning = undefined;
126
+ if (option.kind === "option") {
127
+ if (!draft.selected.delete(option.optionIndex))
128
+ draft.selected.add(option.optionIndex);
234
129
  }
235
- state.warning = undefined;
236
- advanceAfterAnswer();
237
- }
238
- function toggleMultiOption(option) {
239
- const state = currentState();
240
- if (option.kind === "other") {
241
- if (state.customAnswer) {
242
- state.customAnswer.selected = !state.customAnswer.selected;
243
- clearWarning();
244
- }
245
- else {
246
- const tabKey = keyLabel("tui.input.tab");
247
- state.warning = tabKey
248
- ? `Press ${tabKey} to type a custom answer.`
249
- : "Choose Type something to type a custom answer.";
250
- }
251
- return;
130
+ else if (draft.custom) {
131
+ draft.custom.selected = !draft.custom.selected;
252
132
  }
253
- if (state.multiSelected.has(option.optionIndex))
254
- state.multiSelected.delete(option.optionIndex);
255
- else
256
- state.multiSelected.add(option.optionIndex);
257
- clearWarning();
133
+ else {
134
+ const tabKey = keyLabel("tui.input.tab", { keybindings });
135
+ target.warning = tabKey
136
+ ? `Press ${tabKey} to type a custom answer.`
137
+ : "Choose Type something to type a custom answer.";
138
+ }
139
+ refresh();
258
140
  }
259
- function finishReview() {
260
- const missing = firstUnanswered(states);
261
- if (missing !== undefined) {
262
- setCurrentIdx(missing);
263
- currentState().warning = "Answer this question before submitting.";
264
- refresh();
141
+ function submitMulti() {
142
+ const target = state();
143
+ if (hasAnswer(target)) {
144
+ target.warning = undefined;
145
+ advance();
265
146
  return;
266
147
  }
267
- finish({ answers: orderedAnswers(questions, states), outcome: "answered" });
148
+ target.warning = "Select at least one option, type a custom answer, or cancel the questions.";
149
+ refresh();
268
150
  }
269
151
  editor.onSubmit = (value) => {
270
- const trimmed = value.trim();
271
- const question = currentQuestion();
272
- const state = currentState();
273
- if (trimmed.length > QUESTION_LIMITS.userTextChars) {
274
- state.warning = `Keep notes and custom answers under ${QUESTION_LIMITS.userTextChars} characters.`;
152
+ const target = state();
153
+ const text = value.trim();
154
+ if (text.length > QUESTION_LIMITS.userTextChars) {
155
+ target.warning = `Keep notes and custom answers under ${QUESTION_LIMITS.userTextChars} characters.`;
156
+ // The editor clears itself before submitting; give the text back to shorten.
157
+ editor.setText(text);
275
158
  refresh();
276
159
  return;
277
160
  }
278
- if (inputMode === "notes") {
279
- if (noteTarget) {
280
- if (trimmed)
281
- state.notesByOption.set(noteTarget, trimmed);
282
- else
283
- state.notesByOption.delete(noteTarget);
284
- }
285
- inputMode = undefined;
286
- noteTarget = undefined;
287
- notesSnapshot = undefined;
288
- editor.setText("");
289
- syncEditorFocus();
290
- refresh();
161
+ target.warning = undefined;
162
+ const draft = target.draft;
163
+ if (mode.kind === "notes") {
164
+ if (text)
165
+ target.notes.set(mode.option, text);
166
+ else
167
+ target.notes.delete(mode.option);
168
+ setMode({ kind: "choose" });
291
169
  return;
292
170
  }
293
- if (inputMode === "custom") {
294
- if (!trimmed) {
295
- inputMode = undefined;
296
- editor.setText("");
297
- syncEditorFocus();
298
- refresh();
299
- return;
300
- }
301
- if (question.multiSelect) {
302
- state.customAnswer = { text: trimmed, selected: true };
303
- state.optionIndex = question.options.length;
304
- inputMode = undefined;
305
- editor.setText("");
306
- syncEditorFocus();
307
- refresh();
308
- return;
309
- }
310
- state.singleAnswer = {
311
- questionIndex: currentIdx,
312
- question: question.question,
313
- header: question.header,
314
- kind: "custom",
315
- answer: trimmed,
316
- };
317
- inputMode = undefined;
318
- editor.setText("");
319
- syncEditorFocus();
320
- advanceAfterAnswer();
321
- }
322
- };
323
- function handleReviewInput(data) {
324
- if (keyMatches(data, "tui.select.up")) {
325
- reviewScrollOffset = Math.max(0, reviewScrollOffset - VIEWPORT_SCROLL_STEP);
326
- refresh();
171
+ // An empty custom answer leaves any saved one unchanged.
172
+ if (!text) {
173
+ setMode({ kind: "choose" });
327
174
  return;
328
175
  }
329
- if (keyMatches(data, "tui.select.down")) {
330
- reviewScrollOffset = Math.min(reviewMaxScrollOffset, reviewScrollOffset + VIEWPORT_SCROLL_STEP);
331
- refresh();
176
+ if (draft.kind === "multi") {
177
+ draft.custom = { text, selected: true };
178
+ target.focus = options().length - 1;
179
+ setMode({ kind: "choose" });
332
180
  return;
333
181
  }
334
- if (keyMatches(data, "tui.editor.cursorLeft")) {
335
- setCurrentIdx(questions.length - 1);
336
- return;
182
+ draft.choice = { kind: "custom", text };
183
+ setMode({ kind: "choose" });
184
+ advance();
185
+ };
186
+ function handleReviewInput(data) {
187
+ if (keyMatches(data, "tui.select.up") || keyMatches(data, "tui.select.down")) {
188
+ view.scrollReview(keyMatches(data, "tui.select.up") ? -1 : 1);
189
+ refresh();
337
190
  }
338
- if (keyMatches(data, "tui.select.confirm")) {
339
- finishReview();
340
- return;
191
+ else if (keyMatches(data, "tui.select.confirm")) {
192
+ const missing = firstUnanswered(states);
193
+ if (missing === undefined)
194
+ finish("answered");
195
+ else
196
+ goTo(missing, "Answer this question before submitting.");
341
197
  }
342
- if (keyMatches(data, "tui.select.cancel")) {
343
- setCurrentIdx(questions.length - 1);
198
+ else if (keyMatches(data, "tui.editor.cursorLeft") || keyMatches(data, "tui.select.cancel")) {
199
+ goTo(questions.length - 1);
344
200
  }
345
201
  }
346
- function handleQuestionInput(data) {
347
- if (inputMode) {
348
- if (keyMatches(data, "tui.select.cancel")) {
349
- if (inputMode === "notes" && notesSnapshot) {
350
- currentState().singleAnswer = notesSnapshot.prior;
351
- }
352
- inputMode = undefined;
353
- noteTarget = undefined;
354
- notesSnapshot = undefined;
355
- editor.setText("");
356
- syncEditorFocus();
357
- refresh();
358
- return;
359
- }
360
- editor.handleInput(data);
361
- refresh();
202
+ function handleEditorInput(data) {
203
+ if (keyMatches(data, "tui.select.cancel")) {
204
+ discardEdit();
205
+ setMode({ kind: "choose" });
362
206
  return;
363
207
  }
208
+ editor.handleInput(data);
209
+ refresh();
210
+ }
211
+ function handleChoiceInput(data) {
212
+ const chat = mode.kind === "chat";
213
+ const target = state();
214
+ const choices = options();
215
+ const multi = target.draft.kind === "multi";
364
216
  if (keyMatches(data, "tui.editor.cursorLeft")) {
365
- if (currentIdx > 0)
366
- setCurrentIdx(currentIdx - 1);
367
- return;
217
+ if (current > 0)
218
+ goTo(current - 1);
368
219
  }
369
- if (keyMatches(data, "app.question.pageUp") || keyMatches(data, "app.question.pageDown")) {
370
- // Resolve the current pane's height even if another key arrived before a redraw.
371
- render(tui.terminal.columns);
372
- details.page(keyMatches(data, "app.question.pageUp") ? -1 : 1);
373
- refresh();
374
- return;
375
- }
376
- if (keyMatches(data, "tui.editor.cursorRight")) {
377
- if (currentIdx < questions.length - 1)
378
- setCurrentIdx(currentIdx + 1);
220
+ else if (keyMatches(data, "tui.editor.cursorRight")) {
221
+ if (current < questions.length - 1)
222
+ goTo(current + 1);
379
223
  else if (firstUnanswered(states) === undefined)
380
- showReview();
381
- return;
224
+ setMode({ kind: "review" });
382
225
  }
383
- const state = currentState();
384
- const options = currentOptions();
385
- if (keyMatches(data, "tui.select.up")) {
386
- if (footerFocused) {
387
- footerFocused = false;
388
- state.optionIndex = options.length - 1;
389
- }
390
- else if (state.optionIndex > 0) {
391
- state.optionIndex--;
392
- }
393
- clearWarning();
226
+ else if (keyMatches(data, "app.question.pageUp") || keyMatches(data, "app.question.pageDown")) {
227
+ view.pageDetails(snapshot(), keyMatches(data, "app.question.pageUp") ? -1 : 1);
394
228
  refresh();
395
- return;
396
229
  }
397
- if (keyMatches(data, "tui.select.down")) {
398
- if (footerFocused)
230
+ else if (keyMatches(data, "tui.select.up")) {
231
+ target.warning = undefined;
232
+ if (chat) {
233
+ target.focus = choices.length - 1;
234
+ setMode({ kind: "choose" });
399
235
  return;
400
- if (state.optionIndex < options.length - 1)
401
- state.optionIndex++;
402
- else
403
- footerFocused = true;
404
- clearWarning();
405
- refresh();
406
- return;
407
- }
408
- if (keyMatches(data, "tui.input.tab")) {
409
- if (!footerFocused)
410
- beginNotesInput();
411
- return;
412
- }
413
- if (data.length === 1 && data >= "1" && data <= "9") {
414
- const index = data.charCodeAt(0) - 49;
415
- const target = options[index];
416
- if (target) {
417
- footerFocused = false;
418
- state.optionIndex = index;
419
- clearWarning();
420
- if (currentQuestion().multiSelect) {
421
- toggleMultiOption(target);
422
- refresh();
423
- }
424
- else {
425
- recordSingle(target);
426
- }
427
236
  }
428
- return;
237
+ target.focus = Math.max(0, target.focus - 1);
238
+ refresh();
429
239
  }
430
- if (footerFocused) {
431
- if (keyMatches(data, "tui.select.confirm")) {
432
- finish({ answers: orderedAnswers(questions, states), outcome: "needs_clarification" });
240
+ else if (keyMatches(data, "tui.select.down")) {
241
+ if (chat)
433
242
  return;
243
+ target.warning = undefined;
244
+ if (target.focus < choices.length - 1) {
245
+ target.focus++;
246
+ refresh();
434
247
  }
435
- if (keyMatches(data, "tui.select.cancel")) {
436
- finish({ answers: orderedAnswers(questions, states), outcome: "cancelled" });
437
- }
438
- return;
248
+ else
249
+ setMode({ kind: "chat" });
439
250
  }
440
- const question = currentQuestion();
441
- const option = options[state.optionIndex];
442
- if (question.multiSelect && keyMatches(data, "app.list.toggle")) {
443
- if (option)
444
- toggleMultiOption(option);
445
- refresh();
446
- return;
251
+ else if (keyMatches(data, "tui.input.tab")) {
252
+ if (!chat)
253
+ editNotes(choices[target.focus]);
447
254
  }
448
- if (keyMatches(data, "tui.select.confirm")) {
449
- if (question.multiSelect) {
450
- if (option?.kind === "other" && !state.customAnswer) {
451
- beginCustomInput();
452
- return;
453
- }
454
- recordMulti();
455
- }
456
- else if (option)
457
- recordSingle(option);
458
- return;
255
+ else if (data.length === 1 && data >= "1" && data <= "9") {
256
+ const index = data.charCodeAt(0) - 49;
257
+ const option = choices[index];
258
+ if (!option)
259
+ return;
260
+ target.focus = index;
261
+ target.warning = undefined;
262
+ if (chat)
263
+ setMode({ kind: "choose" });
264
+ if (multi)
265
+ toggleMulti(option);
266
+ else
267
+ pickSingle(option);
268
+ }
269
+ else if (chat) {
270
+ if (keyMatches(data, "tui.select.confirm"))
271
+ finish("needs_clarification");
272
+ else if (keyMatches(data, "tui.select.cancel"))
273
+ finish("cancelled");
274
+ }
275
+ else if (multi && keyMatches(data, "app.list.toggle")) {
276
+ toggleMulti(choices[target.focus]);
277
+ }
278
+ else if (keyMatches(data, "tui.select.confirm")) {
279
+ const option = choices[target.focus];
280
+ if (!multi)
281
+ pickSingle(option);
282
+ else if (option.kind === "other" && customText(target) === undefined)
283
+ editCustom();
284
+ else
285
+ submitMulti();
459
286
  }
460
- if (keyMatches(data, "tui.select.cancel")) {
461
- finish({ answers: orderedAnswers(questions, states), outcome: "cancelled" });
287
+ else if (keyMatches(data, "tui.select.cancel")) {
288
+ finish("cancelled");
462
289
  }
463
290
  }
464
- function handleInput(data) {
465
- if (view === "review")
466
- handleReviewInput(data);
467
- else
468
- handleQuestionInput(data);
469
- }
470
291
  function render(width) {
471
- return cache.get(width, tui.terminal.rows, compute);
472
- }
473
- function renderTabs(renderWidth, lines) {
474
- if (questions.length < 2)
475
- return;
476
- const tabCount = questions.length + 1;
477
- const headerWidth = Math.max(4, Math.floor((renderWidth - tabCount * 5) / tabCount));
478
- const tabs = [
479
- ...questions.map((question, index) => {
480
- const answered = hasAnswer(states[index]);
481
- const label = ` ${answered ? "■" : "□"} ${truncateToWidth(question.header, headerWidth)} `;
482
- if (view === "question" && index === currentIdx) {
483
- return theme.bg("selectedBg", theme.fg(answered ? "success" : "text", label));
484
- }
485
- return theme.fg(answered ? "success" : "muted", label);
486
- }),
487
- view === "review"
488
- ? theme.bg("selectedBg", theme.fg("text", " ✓ Submit "))
489
- : theme.fg(firstUnanswered(states) === undefined ? "success" : "dim", " ✓ Submit "),
490
- ].join(" ");
491
- wrapWithPrefix(" ", tabs, renderWidth, lines);
492
- }
493
- function previewLines(previewText, width) {
494
- return previewCache.get(previewText, width, () => new Markdown(previewText, 1, 0, getMarkdownTheme()).render(Math.max(1, width)));
495
- }
496
- /** Multi-select checkbox marker; the other row shows no box until a custom answer exists. */
497
- function multiMarker(option, checked, hasCustomAnswer) {
498
- if (option.kind === "other" && !hasCustomAnswer)
499
- return " ";
500
- return checked ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
501
- }
502
- function renderReview(renderWidth, lines) {
503
- wrapWithPrefix(" ", theme.fg("accent", "Review answers"), renderWidth, lines);
504
- lines.push("");
505
- for (const answer of orderedAnswers(questions, states)) {
506
- wrapWithPrefix(" ", theme.fg("text", `${answer.questionIndex + 1}. ${answer.question}`), renderWidth, lines);
507
- const response = answer.kind === "multi"
508
- ? (answer.selected?.join(", ") ?? "(no input)")
509
- : (answer.answer ?? "(no input)");
510
- wrapWithPrefix(" ", theme.fg("success", response), renderWidth, lines);
511
- for (const note of answer.notes ?? []) {
512
- wrapWithPrefix(" ", theme.fg("muted", `Note for ${note.option}: ${note.text}`), renderWidth, lines);
513
- }
514
- lines.push("");
515
- }
516
- wrapWithPrefix(" ", theme.fg("dim", joinHints(keyAction("tui.select.confirm", "submit"), keyAction("tui.select.cancel", "edit last question"), keyGroupAction(["tui.select.up", "tui.select.down"], "scroll"))), renderWidth, lines);
517
- }
518
- /** Chat row, warning, and key hints: always visible below the scrolling option area. */
519
- function renderQuestionFooter(renderWidth, digitRange) {
520
- const state = currentState();
521
- const isMulti = currentQuestion().multiSelect === true;
522
- const lines = [];
523
- const chatPrefix = footerFocused ? theme.fg("accent", "→") : " ";
524
- wrapWithPrefix(`${chatPrefix} `, theme.fg(footerFocused ? "accent" : "text", "Chat about this"), renderWidth, lines);
525
- if (state.warning) {
526
- lines.push("");
527
- wrapWithPrefix(" ", theme.fg("warning", state.warning), renderWidth, lines);
528
- }
529
- let hints;
530
- if (inputMode === "notes") {
531
- hints = joinHints(editorSubmitAction("save notes"), keyAction("tui.select.cancel", "back"));
532
- }
533
- else if (inputMode === "custom") {
534
- hints = joinHints(editorSubmitAction(isMulti ? "save custom answer" : "continue"), keyAction("tui.select.cancel", "back"));
535
- }
536
- else if (footerFocused) {
537
- hints = joinHints(keyAction("tui.select.confirm", "discuss"), keyAction("tui.select.up", "return to options"), keyAction("tui.select.cancel", "cancel"));
538
- }
539
- else if (isMulti) {
540
- hints = joinHints(keyGroupAction(["tui.select.up", "tui.select.down"], "navigate"), `${digitRange} toggle`, keyAction("app.list.toggle", "toggle focused"), keyAction("tui.input.tab", "notes/custom"), keyAction("tui.select.confirm", "continue"), questions.length > 1
541
- ? keyGroupAction(["tui.editor.cursorLeft", "tui.editor.cursorRight"], "questions")
542
- : "", keyAction("tui.select.cancel", "cancel"));
543
- }
544
- else {
545
- hints = joinHints(keyGroupAction(["tui.select.up", "tui.select.down"], "navigate"), `${digitRange} select`, keyAction("tui.input.tab", "notes/custom"), keyAction("tui.select.confirm", "select"), questions.length > 1
546
- ? keyGroupAction(["tui.editor.cursorLeft", "tui.editor.cursorRight"], "questions")
547
- : "", keyAction("tui.select.cancel", "cancel"));
548
- }
549
- // Wrap between actions, so a key stays beside the action it performs.
550
- let hintLine = "";
551
- for (const hint of hints.split(" • ")) {
552
- const joined = hintLine ? `${hintLine} • ${hint}` : hint;
553
- if (hintLine && visibleWidth(joined) > renderWidth - 1) {
554
- wrapWithPrefix(" ", theme.fg("dim", hintLine), renderWidth, lines);
555
- hintLine = hint;
556
- }
557
- else
558
- hintLine = joined;
559
- }
560
- if (hintLine)
561
- wrapWithPrefix(" ", theme.fg("dim", hintLine), renderWidth, lines);
562
- lines.push(ruleBorder(theme, renderWidth));
563
- return lines;
564
- }
565
- /**
566
- * Show whole items from `optionLines` in `rows` rows around `focus`, with
567
- * "more options" hints on the sides that have hidden items. `focusRow` is the
568
- * row of the focused item's first line.
569
- */
570
- function windowOptionLines(optionLines, items, focus, rows, width) {
571
- const window = windowItems(items, focus, rows);
572
- const rowsBefore = (count) => items.slice(0, count).reduce((sum, item) => sum + item.height, 0);
573
- const from = rowsBefore(window.first);
574
- const block = optionLines.slice(from, rowsBefore(window.last + 1));
575
- const visible = applyVerticalViewport(block, width, window.contentRows, theme, {
576
- anchorLine: Math.max(0, block.findIndex((line) => line.includes(CURSOR_MARKER))),
577
- }).lines;
578
- if (!window.showAbove && !window.showBelow)
579
- return { rows: visible, focusRow: rowsBefore(focus) - from };
580
- const hint = (direction, count) => theme.fg("dim", ` ${truncateToWidth(moreOptionsHint(direction, count), Math.max(1, width - 1))}`);
581
- const spare = Array.from({ length: Math.max(0, window.contentRows - visible.length) }, () => "");
582
- return {
583
- rows: [
584
- ...(window.showAbove ? [hint("up", window.hiddenAbove)] : []),
585
- ...visible,
586
- ...(window.showBelow ? [hint("down", window.hiddenBelow)] : []),
587
- ...spare,
588
- ],
589
- focusRow: (window.showAbove ? 1 : 0) + rowsBefore(focus) - from,
590
- };
591
- }
592
- function compute(width, terminalRows) {
593
- const lines = [];
594
- const renderWidth = Math.max(1, width);
595
- const maxRows = dialogRowBudget(terminalRows);
596
- lines.push(ruleBorder(theme, renderWidth));
597
- renderTabs(renderWidth, lines);
598
- if (view === "review") {
599
- renderReview(renderWidth, lines);
600
- lines.push(ruleBorder(theme, renderWidth));
601
- const viewport = applyVerticalViewport(lines, renderWidth, maxRows, theme, {
602
- preferredStart: reviewScrollOffset,
603
- });
604
- reviewMaxScrollOffset = viewport.maxStart;
605
- reviewScrollOffset = viewport.start;
606
- return viewport.lines;
607
- }
608
- const question = currentQuestion();
609
- const state = currentState();
610
- const options = currentOptions();
611
- const isMulti = question.multiSelect === true;
612
- wrapWithPrefix(" ", theme.fg("text", question.question), renderWidth, lines);
613
- // The pinned head and footer leave one bounded area for choices and reading.
614
- const footerLines = renderQuestionFooter(renderWidth, `1-${Math.min(9, options.length)}`);
615
- const areaRows = Math.max(1, maxRows - lines.length - footerLines.length);
616
- const sideBySide = !inputMode && renderWidth >= 72;
617
- // Measure every authored label, reserving the six-column prefix and a checkmark.
618
- // Saved answers and focus changes must not move the divider or reflow the preview.
619
- const listWidth = sideBySide
620
- ? Math.min(Math.floor((renderWidth - 3) / 2), Math.max(24, ...options.map((option, index) => 6 + visibleWidth(`${index + 1}. ${option.label.replace(/\s+/gu, " ")}`) + 2)))
621
- : renderWidth;
622
- const detailWidth = sideBySide ? renderWidth - listWidth - 3 : renderWidth;
623
- const optionLines = [];
624
- const items = [];
625
- let focusItem = footerFocused ? options.length - 1 : state.optionIndex;
626
- for (let index = 0; index < options.length; index++) {
627
- const option = options[index];
628
- const focused = !footerFocused && index === state.optionIndex;
629
- const checked = isMulti &&
630
- ((option.kind === "option" && state.multiSelected.has(option.optionIndex)) ||
631
- (option.kind === "other" && state.customAnswer?.selected === true));
632
- const selectedSingle = !isMulti &&
633
- (option.kind === "other"
634
- ? state.singleAnswer?.kind === "custom"
635
- : state.singleAnswer?.answer === option.label);
636
- const marker = isMulti ? multiMarker(option, checked, state.customAnswer !== undefined) : " ";
637
- const focusArrow = focused ? theme.fg("accent", "→") : " ";
638
- const note = option.kind === "option" && state.notesByOption.has(option.label) ? theme.fg("success", " +note") : "";
639
- const customText = option.kind === "other"
640
- ? (state.customAnswer?.text ??
641
- (state.singleAnswer?.kind === "custom" ? state.singleAnswer.answer : undefined))
642
- : undefined;
643
- const labelText = (customText ? `${option.label} ✎ ${customText}` : option.label).replace(/\s+/gu, " ");
644
- const label = `${index + 1}. ${labelText}${selectedSingle ? " ✓" : ""}${note}`;
645
- const color = focused ? "accent" : selectedSingle || checked ? "success" : "text";
646
- const row = truncateToWidth(`${focusArrow} ${marker} ${theme.fg(color, label)}`, listWidth);
647
- optionLines.push(focused ? theme.bg("selectedBg", row + " ".repeat(Math.max(0, listWidth - visibleWidth(row)))) : row);
648
- items.push({ height: 1, countsAsOption: true });
649
- }
650
- if (inputMode) {
651
- const start = optionLines.length;
652
- const label = inputMode === "notes" ? `Notes for ${noteTarget ?? "option"}:` : "Your answer:";
653
- optionLines.push(theme.fg("muted", truncateToWidth(` ${label}`, listWidth)));
654
- for (const line of editor.render(Math.max(1, listWidth - 2)))
655
- optionLines.push(` ${line}`);
656
- items.push({ height: optionLines.length - start, countsAsOption: false });
657
- focusItem = items.length - 1;
658
- }
659
- const regionStart = lines.length;
660
- let focusRow;
661
- if (inputMode) {
662
- const windowed = windowOptionLines(optionLines, items, focusItem, areaRows, renderWidth);
663
- focusRow = windowed.focusRow;
664
- lines.push(...windowed.rows);
665
- }
666
- else {
667
- const option = options[state.optionIndex];
668
- const content = [];
669
- const hasPreview = !footerFocused && !isMulti && option.kind === "option" && Boolean(option.preview);
670
- const title = footerFocused ? "Chat about this" : `${hasPreview ? "Preview" : "Details"} · ${option.label}`;
671
- // The compact list may elide a long label; its full text stays readable here.
672
- if (!footerFocused &&
673
- visibleWidth(`${state.optionIndex + 1}. ${option.label.replace(/\s+/gu, " ")}`) > listWidth - 6) {
674
- wrapWithPrefix(" ", theme.fg("text", option.label), detailWidth, content);
675
- }
676
- if (footerFocused) {
677
- wrapWithPrefix(" ", theme.fg("muted", "Discuss this question before choosing an answer."), detailWidth, content);
678
- }
679
- else if (option.kind === "option") {
680
- wrapWithPrefix(" ", theme.fg("muted", option.description), detailWidth, content);
681
- const note = state.notesByOption.get(option.label);
682
- if (note)
683
- wrapWithPrefix(" ", theme.fg("success", `Note: ${note}`), detailWidth, content);
684
- if (hasPreview && option.preview)
685
- content.push("", ...previewLines(option.preview, detailWidth));
686
- }
687
- else {
688
- const custom = state.customAnswer?.text ??
689
- (state.singleAnswer?.kind === "custom" ? state.singleAnswer.answer : undefined);
690
- wrapWithPrefix(" ", theme.fg("muted", custom || "Write your own answer instead of choosing an option."), detailWidth, content);
691
- }
692
- // On narrow screens reserve reading space first, then show as many compact
693
- // choices as fit. The focused choice always remains in the list window.
694
- const optionRows = sideBySide ? areaRows : Math.max(1, Math.min(options.length, areaRows - 4));
695
- const detailRows = sideBySide ? areaRows : Math.max(1, areaRows - optionRows);
696
- const left = windowOptionLines(optionLines, items, focusItem, optionRows, listWidth);
697
- const right = details.render(`${currentIdx}:${state.optionIndex}:${footerFocused}`, title, content, detailWidth, detailRows, keyGroupAction(["app.question.pageUp", "app.question.pageDown"], "scroll"), theme);
698
- focusRow = left.focusRow;
699
- if (sideBySide) {
700
- for (let row = 0; row < Math.max(left.rows.length, right.length); row++) {
701
- const leftText = left.rows[row] ?? "";
702
- const leftPadded = leftText + " ".repeat(Math.max(0, listWidth - visibleWidth(leftText)));
703
- lines.push(`${leftPadded}${theme.fg("dim", " │ ")}${right[row] ?? ""}`);
704
- }
705
- }
706
- else {
707
- lines.push(...left.rows, ...right);
708
- }
709
- }
710
- const anchorLine = regionStart + focusRow;
711
- lines.push(...footerLines);
712
- // Only a terminal too short for the pinned rows has lines left to drop here.
713
- return applyVerticalViewport(lines, renderWidth, maxRows, theme, { anchorLine }).lines;
292
+ return cache.get(width, tui.terminal.rows, (renderWidth, rows) => view.render(snapshot(), renderWidth, rows));
714
293
  }
715
294
  return {
716
295
  get focused() {
@@ -718,13 +297,20 @@ export function createQuestionDialog(questions, signal) {
718
297
  },
719
298
  set focused(value) {
720
299
  dialogFocused = value;
721
- syncEditorFocus();
300
+ editor.focused = dialogFocused && isEditing();
722
301
  },
723
302
  render,
724
303
  invalidate: () => {
725
304
  cache.invalidate();
726
305
  },
727
- handleInput,
306
+ handleInput(data) {
307
+ if (mode.kind === "review")
308
+ handleReviewInput(data);
309
+ else if (isEditing())
310
+ handleEditorInput(data);
311
+ else
312
+ handleChoiceInput(data);
313
+ },
728
314
  dispose() {
729
315
  signal?.removeEventListener("abort", onAbort);
730
316
  },