privateer-agent 0.15.4 → 0.15.6

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 (229) hide show
  1. package/README.md +11 -0
  2. package/bin/boot-bundle.d.mts +2 -0
  3. package/bin/boot-bundle.mjs +44 -0
  4. package/bin/fresh-supervisor.d.mts +5 -1
  5. package/bin/fresh-supervisor.mjs +47 -10
  6. package/bin/privateer-launch.mjs +27 -3
  7. package/bin/privateer-subagent.mjs +5 -2
  8. package/bin/privateer-verify.mjs +12 -0
  9. package/bin/session-recovery.d.mts +53 -0
  10. package/bin/session-recovery.mjs +240 -0
  11. package/bin/session-seal-preload.mjs +17 -0
  12. package/bin/session-seal.d.mts +25 -0
  13. package/bin/session-seal.mjs +242 -0
  14. package/dist-boot/boot.mjs +3 -0
  15. package/dist-boot/c-27JKSMWL.mjs +1460 -0
  16. package/dist-boot/c-2BMRFV24.mjs +269 -0
  17. package/dist-boot/c-2CTF6JA3.mjs +166 -0
  18. package/dist-boot/c-2G3BBHRJ.mjs +84 -0
  19. package/dist-boot/c-2IDLKXX6.mjs +250 -0
  20. package/dist-boot/c-2NJD5VYM.mjs +8 -0
  21. package/dist-boot/c-2S5EEQPW.mjs +1602 -0
  22. package/dist-boot/c-2WRDCUVI.mjs +25 -0
  23. package/dist-boot/c-2YN3M256.mjs +14 -0
  24. package/dist-boot/c-37GM3GMU.mjs +187 -0
  25. package/dist-boot/c-3STKRSVO.mjs +2480 -0
  26. package/dist-boot/c-3TPV6TQQ.mjs +2320 -0
  27. package/dist-boot/c-3UVHLQDJ.mjs +225 -0
  28. package/dist-boot/c-42PUUZYC.mjs +697 -0
  29. package/dist-boot/c-4D5UMWVW.mjs +683 -0
  30. package/dist-boot/c-4ETZRMTA.mjs +46 -0
  31. package/dist-boot/c-4QT25BD5.mjs +1482 -0
  32. package/dist-boot/c-4WVR32AP.mjs +2579 -0
  33. package/dist-boot/c-5EMM567G.mjs +152 -0
  34. package/dist-boot/c-5FA4CV63.mjs +145 -0
  35. package/dist-boot/c-5GV3X3RB.mjs +14 -0
  36. package/dist-boot/c-5JCEPRIY.mjs +52 -0
  37. package/dist-boot/c-5NDKIIWT.mjs +16 -0
  38. package/dist-boot/c-5QL3AR26.mjs +4665 -0
  39. package/dist-boot/c-5XY6MFKN.mjs +5142 -0
  40. package/dist-boot/c-65UZIZC7.mjs +2897 -0
  41. package/dist-boot/c-6FUXKHOK.mjs +1969 -0
  42. package/dist-boot/c-6JHWQE4V.mjs +18242 -0
  43. package/dist-boot/c-6LOBR4I6.mjs +5280 -0
  44. package/dist-boot/c-6QHOZXKQ.mjs +14394 -0
  45. package/dist-boot/c-6ZO2SADQ.mjs +178 -0
  46. package/dist-boot/c-6ZORVVZP.mjs +1449 -0
  47. package/dist-boot/c-76QH4UCP.mjs +2441 -0
  48. package/dist-boot/c-77GO5L5Y.mjs +20982 -0
  49. package/dist-boot/c-7M23XFTN.mjs +5052 -0
  50. package/dist-boot/c-7TC367E2.mjs +32 -0
  51. package/dist-boot/c-7UOG7POX.mjs +1524 -0
  52. package/dist-boot/c-7VHP3FLN.mjs +376 -0
  53. package/dist-boot/c-A73JHZP2.mjs +13207 -0
  54. package/dist-boot/c-ABXULAFL.mjs +71 -0
  55. package/dist-boot/c-ANDD2AQO.mjs +74 -0
  56. package/dist-boot/c-AOY77AZL.mjs +1430 -0
  57. package/dist-boot/c-AXEPZ22I.mjs +108 -0
  58. package/dist-boot/c-B2Y7VNHU.mjs +427 -0
  59. package/dist-boot/c-B7VJUH73.mjs +77 -0
  60. package/dist-boot/c-BFW6CDVL.mjs +24 -0
  61. package/dist-boot/c-BHNIF2AR.mjs +188 -0
  62. package/dist-boot/c-BKF2O6BL.mjs +23 -0
  63. package/dist-boot/c-BOZXFTZN.mjs +8 -0
  64. package/dist-boot/c-BRNYJWZL.mjs +28 -0
  65. package/dist-boot/c-C3N4Z2HO.mjs +93 -0
  66. package/dist-boot/c-C4IV7ZFG.mjs +717 -0
  67. package/dist-boot/c-C5NK6UYV.mjs +793 -0
  68. package/dist-boot/c-C67644MB.mjs +1659 -0
  69. package/dist-boot/c-C6S2YB53.mjs +620 -0
  70. package/dist-boot/c-CDBEJSBF.mjs +37 -0
  71. package/dist-boot/c-CG47HBKU.mjs +435 -0
  72. package/dist-boot/c-CKCTWCT5.mjs +51 -0
  73. package/dist-boot/c-CN77DCGF.mjs +60 -0
  74. package/dist-boot/c-CRBXNKFO.mjs +1795 -0
  75. package/dist-boot/c-CRIHASQ6.mjs +973 -0
  76. package/dist-boot/c-CU4NGAUO.mjs +3786 -0
  77. package/dist-boot/c-CWRFLGQU.mjs +1883 -0
  78. package/dist-boot/c-CYQB2USO.mjs +32 -0
  79. package/dist-boot/c-DEE67DJO.mjs +379 -0
  80. package/dist-boot/c-DIFDBYOP.mjs +1258 -0
  81. package/dist-boot/c-DOSGVXDZ.mjs +34879 -0
  82. package/dist-boot/c-DR7XQI32.mjs +381 -0
  83. package/dist-boot/c-DTGPSB46.mjs +14666 -0
  84. package/dist-boot/c-DTHDNA4P.mjs +320 -0
  85. package/dist-boot/c-E4Z3Y5B5.mjs +3864 -0
  86. package/dist-boot/c-E66Z2TI7.mjs +203 -0
  87. package/dist-boot/c-EBU6RLRV.mjs +1527 -0
  88. package/dist-boot/c-EBZZNGSF.mjs +432 -0
  89. package/dist-boot/c-EC3LJ2KF.mjs +1032 -0
  90. package/dist-boot/c-EQBKXWNX.mjs +15829 -0
  91. package/dist-boot/c-EUEPIW4S.mjs +574 -0
  92. package/dist-boot/c-EX4YVWAT.mjs +378 -0
  93. package/dist-boot/c-EYZGSAJU.mjs +98 -0
  94. package/dist-boot/c-F75VRKY4.mjs +172 -0
  95. package/dist-boot/c-FH4NXKHV.mjs +177 -0
  96. package/dist-boot/c-FHNUCTEM.mjs +140 -0
  97. package/dist-boot/c-FJ5T42DS.mjs +5349 -0
  98. package/dist-boot/c-FPCNRL7G.mjs +1728 -0
  99. package/dist-boot/c-G4DLN7Q4.mjs +8 -0
  100. package/dist-boot/c-G56GYQFZ.mjs +2507 -0
  101. package/dist-boot/c-GLIOERDQ.mjs +1200 -0
  102. package/dist-boot/c-GMJ5HUBM.mjs +14 -0
  103. package/dist-boot/c-H3HSGMAQ.mjs +461 -0
  104. package/dist-boot/c-HIKTO6P7.mjs +275 -0
  105. package/dist-boot/c-I2MCJ34O.mjs +464 -0
  106. package/dist-boot/c-I3C6LBW4.mjs +765 -0
  107. package/dist-boot/c-I54OKQHW.mjs +387 -0
  108. package/dist-boot/c-IFSP3R4D.mjs +80 -0
  109. package/dist-boot/c-IZK77VB7.mjs +171 -0
  110. package/dist-boot/c-J3Y25UGE.mjs +54 -0
  111. package/dist-boot/c-J5IESS7W.mjs +27750 -0
  112. package/dist-boot/c-JJWZBY7L.mjs +254 -0
  113. package/dist-boot/c-JKTFXTGF.mjs +463 -0
  114. package/dist-boot/c-JN57AXQ4.mjs +77 -0
  115. package/dist-boot/c-KFIO6LUW.mjs +1096 -0
  116. package/dist-boot/c-KIEMEFWW.mjs +478 -0
  117. package/dist-boot/c-KIVXWBYD.mjs +28 -0
  118. package/dist-boot/c-KP6CRIP4.mjs +580 -0
  119. package/dist-boot/c-L26VOJX4.mjs +11558 -0
  120. package/dist-boot/c-L5GHWG5G.mjs +5988 -0
  121. package/dist-boot/c-M7NSQAKK.mjs +353 -0
  122. package/dist-boot/c-MCEWOPBW.mjs +56 -0
  123. package/dist-boot/c-MGPIEG7I.mjs +20 -0
  124. package/dist-boot/c-NREXJV3M.mjs +393 -0
  125. package/dist-boot/c-O5PP6CTX.mjs +162 -0
  126. package/dist-boot/c-OBQDHSXM.mjs +1774 -0
  127. package/dist-boot/c-OLQHHDD4.mjs +21 -0
  128. package/dist-boot/c-OXW4JQQH.mjs +319 -0
  129. package/dist-boot/c-P3D7ZUWN.mjs +1288 -0
  130. package/dist-boot/c-P4PQZVV5.mjs +948 -0
  131. package/dist-boot/c-P63T3XL2.mjs +26373 -0
  132. package/dist-boot/c-P6N5DQGZ.mjs +194 -0
  133. package/dist-boot/c-P7MLKQFC.mjs +58 -0
  134. package/dist-boot/c-PB3PZJL5.mjs +1276 -0
  135. package/dist-boot/c-PTN4S6G2.mjs +1259 -0
  136. package/dist-boot/c-PYCE5J7U.mjs +1083 -0
  137. package/dist-boot/c-Q4HYCRLB.mjs +19410 -0
  138. package/dist-boot/c-QUXMETGG.mjs +68 -0
  139. package/dist-boot/c-RDMJZCOX.mjs +356 -0
  140. package/dist-boot/c-ROGJK76M.mjs +17 -0
  141. package/dist-boot/c-RQJJ26T4.mjs +708 -0
  142. package/dist-boot/c-RSKCUPJF.mjs +7559 -0
  143. package/dist-boot/c-RVJU4O6P.mjs +37 -0
  144. package/dist-boot/c-S2RVHR3M.mjs +165 -0
  145. package/dist-boot/c-SKMK3PEE.mjs +91120 -0
  146. package/dist-boot/c-TI7SM4EG.mjs +20 -0
  147. package/dist-boot/c-TILJ6GXD.mjs +1235 -0
  148. package/dist-boot/c-TT6BCUJD.mjs +114 -0
  149. package/dist-boot/c-U4ITOXAN.mjs +2107 -0
  150. package/dist-boot/c-UGERXVO2.mjs +7120 -0
  151. package/dist-boot/c-UPBH5AXY.mjs +1363 -0
  152. package/dist-boot/c-US2PUNZL.mjs +597 -0
  153. package/dist-boot/c-UU4WZPPE.mjs +20 -0
  154. package/dist-boot/c-UWVIYPQS.mjs +39 -0
  155. package/dist-boot/c-V4RHQBPQ.mjs +17683 -0
  156. package/dist-boot/c-V5MRVK47.mjs +351 -0
  157. package/dist-boot/c-V6CL266R.mjs +1755 -0
  158. package/dist-boot/c-VJTMFKYE.mjs +14975 -0
  159. package/dist-boot/c-VJTRUKIB.mjs +773 -0
  160. package/dist-boot/c-W4IFSHPW.mjs +43 -0
  161. package/dist-boot/c-WETGB2CF.mjs +113 -0
  162. package/dist-boot/c-WO3QSZUU.mjs +1337 -0
  163. package/dist-boot/c-WQLH73TY.mjs +242 -0
  164. package/dist-boot/c-XL735JW5.mjs +500 -0
  165. package/dist-boot/c-Y7HADL5B.mjs +1107 -0
  166. package/dist-boot/c-YGQHSVSK.mjs +41 -0
  167. package/dist-boot/c-YMP2AMEB.mjs +10350 -0
  168. package/dist-boot/c-YRDO2WEF.mjs +12012 -0
  169. package/dist-boot/c-YUSTRATJ.mjs +8 -0
  170. package/dist-boot/c-Z2262R22.mjs +2697 -0
  171. package/dist-boot/c-Z4ALQMJC.mjs +709 -0
  172. package/dist-boot/c-Z5ZUQVUY.mjs +62 -0
  173. package/dist-boot/c-ZDZUKXF7.mjs +874 -0
  174. package/dist-boot/c-ZJYLGIC3.mjs +32 -0
  175. package/dist-boot/c-ZNA5CDMI.mjs +77 -0
  176. package/dist-boot/c-ZSO6DI3M.mjs +204 -0
  177. package/dist-boot/c-ZWUVHKMK.mjs +100 -0
  178. package/dist-boot/c-ZY4MESEW.mjs +832 -0
  179. package/dist-boot/c-ZZ5REI4L.mjs +329 -0
  180. package/dist-boot/cli.mjs +51 -0
  181. package/dist-boot/ext-anthropic-attribution.mjs +207 -0
  182. package/dist-boot/ext-pi-background-tasks.mjs +7234 -0
  183. package/dist-boot/ext-pi-subagents.mjs +37 -0
  184. package/dist-boot/ext-privateer-account.mjs +4426 -0
  185. package/dist-boot/ext-privateer-brand.mjs +5274 -0
  186. package/dist-boot/ext-privateer-codemode.mjs +37 -0
  187. package/dist-boot/ext-privateer-computer.mjs +626 -0
  188. package/dist-boot/ext-privateer-connect.mjs +1862 -0
  189. package/dist-boot/ext-privateer-context.mjs +323 -0
  190. package/dist-boot/ext-privateer-desktop.mjs +92 -0
  191. package/dist-boot/ext-privateer-final-audit.mjs +48 -0
  192. package/dist-boot/ext-privateer-fresh.mjs +81 -0
  193. package/dist-boot/ext-privateer-gate.mjs +5959 -0
  194. package/dist-boot/ext-privateer-hints.mjs +236 -0
  195. package/dist-boot/ext-privateer-media.mjs +2626 -0
  196. package/dist-boot/ext-privateer-models.mjs +3397 -0
  197. package/dist-boot/ext-privateer-posture.mjs +3006 -0
  198. package/dist-boot/ext-privateer-privacy.mjs +4578 -0
  199. package/dist-boot/ext-privateer-recovery.mjs +287 -0
  200. package/dist-boot/ext-privateer-speak.mjs +4754 -0
  201. package/dist-boot/ext-privateer-terminals.mjs +725 -0
  202. package/dist-boot/ext-privateer-tools.mjs +528 -0
  203. package/dist-boot/ext-privateer-truncation.mjs +74 -0
  204. package/dist-boot/ext-privateer-update.mjs +210 -0
  205. package/dist-boot/ext-privateer-vision.mjs +148 -0
  206. package/dist-boot/ext-privateer-web.mjs +543 -0
  207. package/dist-boot/ext-rpiv-ask-user-question.mjs +710 -0
  208. package/dist-boot/manifest.json +9 -0
  209. package/extensions/privateer-brand.ts +3 -1
  210. package/extensions/privateer-context.ts +4 -2
  211. package/extensions/privateer-final-audit.ts +17 -0
  212. package/extensions/privateer-gate.ts +55 -9
  213. package/extensions/privateer-recovery.ts +175 -0
  214. package/extensions/privateer-web.ts +4 -4
  215. package/package.json +3 -1
  216. package/patches/@earendil-works+pi-coding-agent+1.3.0.patch +199 -2
  217. package/patches/pi-background-tasks+2.6.10.patch +91 -7
  218. package/src/cli/chat.ts +7 -1
  219. package/src/config/moatManifest.json +2 -0
  220. package/src/config/whatsNew.json +4 -4
  221. package/src/context.ts +33 -5
  222. package/src/engine/finalAudit.ts +38 -0
  223. package/src/engine/stallWatchdog.ts +8 -1
  224. package/src/providers/account.ts +34 -3
  225. package/src/remote/attentionPush.ts +40 -0
  226. package/src/remote/liveTaskSession.ts +7 -1
  227. package/src/remote/relayClient.ts +38 -4
  228. package/src/remote/remoteBridge.ts +17 -0
  229. package/src/remote/transcriptSnapshot.ts +104 -0
@@ -0,0 +1,2507 @@
1
+ import{createRequire as __pvCR}from"node:module";import{fileURLToPath as __pvF}from"node:url";import{dirname as __pvD}from"node:path";const require=__pvCR(import.meta.url);const __pvUrl=(r)=>new URL(r,import.meta.url).href;const __pvFile=(r)=>__pvF(new URL(r,import.meta.url));
2
+ import {
3
+ COLLAPSE_KEY_OFF,
4
+ DEFAULT_COLLAPSE_KEY,
5
+ ROW_INTENT_META,
6
+ displayLabel,
7
+ formatAnswerScalar,
8
+ formatKeySpecForDisplay,
9
+ t
10
+ } from "./c-2IDLKXX6.mjs";
11
+ import "./c-B2Y7VNHU.mjs";
12
+ import {
13
+ DynamicBorder
14
+ } from "./c-SKMK3PEE.mjs";
15
+ import "./c-I54OKQHW.mjs";
16
+ import "./c-UPBH5AXY.mjs";
17
+ import "./c-7UOG7POX.mjs";
18
+ import "./c-E4Z3Y5B5.mjs";
19
+ import "./c-6ZO2SADQ.mjs";
20
+ import "./c-JKTFXTGF.mjs";
21
+ import {
22
+ getMarkdownTheme
23
+ } from "./c-CRBXNKFO.mjs";
24
+ import "./c-KP6CRIP4.mjs";
25
+ import "./c-RSKCUPJF.mjs";
26
+ import "./c-PTN4S6G2.mjs";
27
+ import "./c-P6N5DQGZ.mjs";
28
+ import "./c-6LOBR4I6.mjs";
29
+ import "./c-3STKRSVO.mjs";
30
+ import "./c-YRDO2WEF.mjs";
31
+ import "./c-BHNIF2AR.mjs";
32
+ import "./c-ABXULAFL.mjs";
33
+ import "./c-2G3BBHRJ.mjs";
34
+ import "./c-O5PP6CTX.mjs";
35
+ import "./c-CDBEJSBF.mjs";
36
+ import "./c-DEE67DJO.mjs";
37
+ import "./c-WO3QSZUU.mjs";
38
+ import "./c-S2RVHR3M.mjs";
39
+ import {
40
+ CURSOR_MARKER,
41
+ Container,
42
+ Editor,
43
+ Key,
44
+ Markdown,
45
+ Spacer,
46
+ Text,
47
+ matchesKey,
48
+ truncateToWidth,
49
+ visibleWidth,
50
+ wrapTextWithAnsi
51
+ } from "./c-EQBKXWNX.mjs";
52
+ import "./c-RQJJ26T4.mjs";
53
+ import "./c-L5GHWG5G.mjs";
54
+ import {
55
+ __name
56
+ } from "./c-CN77DCGF.mjs";
57
+
58
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/scroll-window.ts?ext=rpiv-ask-user-question
59
+ var OVERFLOW_UP = "\u2191";
60
+ var OVERFLOW_DOWN = "\u2193";
61
+ var OVERFLOW_BOTH = "\u2195";
62
+ function computeScrollStart(bodyRange, rowsBeforeBody, availableMiddle, middleRows) {
63
+ if (!bodyRange) return 0;
64
+ const focusedRowInMiddle = rowsBeforeBody + bodyRange[0];
65
+ const focusedHeight = bodyRange[1] - bodyRange[0];
66
+ const idealStart = focusedRowInMiddle - Math.floor(Math.max(0, availableMiddle - focusedHeight) / 2);
67
+ return Math.max(0, Math.min(idealStart, middleRows - availableMiddle));
68
+ }
69
+ __name(computeScrollStart, "computeScrollStart");
70
+ function decorateOverflow(scrollableMiddle, hasUp, hasDown, theme, protectedRows) {
71
+ const drawable = /* @__PURE__ */ __name((row) => !protectedRows || row < protectedRows[0] || row >= protectedRows[1], "drawable");
72
+ if (hasUp && hasDown && scrollableMiddle.length === 1) {
73
+ if (drawable(0)) scrollableMiddle[0] = theme.fg("dim", OVERFLOW_BOTH);
74
+ return;
75
+ }
76
+ if (hasUp && scrollableMiddle.length > 0 && drawable(0)) {
77
+ scrollableMiddle[0] = theme.fg("dim", OVERFLOW_UP);
78
+ }
79
+ const last = scrollableMiddle.length - 1;
80
+ if (hasDown && last > 0 && drawable(last)) {
81
+ scrollableMiddle[last] = theme.fg("dim", OVERFLOW_DOWN);
82
+ }
83
+ }
84
+ __name(decorateOverflow, "decorateOverflow");
85
+ function windowLines(lines, rows, focus, theme) {
86
+ if (lines.length <= rows) return { lines: [...lines], focus };
87
+ const start = computeScrollStart(focus, 0, rows, lines.length);
88
+ const visible = lines.slice(start, start + rows);
89
+ const inWindow = focus && [focus[0] - start, focus[1] - start];
90
+ decorateOverflow(visible, start > 0, start + rows < lines.length, theme, inWindow);
91
+ return { lines: visible, focus: inWindow };
92
+ }
93
+ __name(windowLines, "windowLines");
94
+
95
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/derivations.ts?ext=rpiv-ask-user-question
96
+ function selectConfirmedIndicator(questions, currentTab, answers, items) {
97
+ const q = questions[currentTab];
98
+ if (!q || q.multiSelect === true) return void 0;
99
+ const prior = answers.get(currentTab);
100
+ if (!prior) return void 0;
101
+ if (prior.kind === "custom") {
102
+ const otherIndex = items.findIndex((it) => it.kind === "other");
103
+ if (otherIndex < 0) return void 0;
104
+ return { index: otherIndex, labelOverride: prior.answer ?? "" };
105
+ }
106
+ if (prior.kind !== "option" || typeof prior.answer !== "string") return void 0;
107
+ const index = items.findIndex((it) => it.kind === "option" && it.label === prior.answer);
108
+ if (index < 0) return void 0;
109
+ return { index };
110
+ }
111
+ __name(selectConfirmedIndicator, "selectConfirmedIndicator");
112
+ function selectActivePreviewPaneIndex(currentTab, totalQuestions) {
113
+ if (totalQuestions <= 0) return 0;
114
+ return Math.min(currentTab, totalQuestions - 1);
115
+ }
116
+ __name(selectActivePreviewPaneIndex, "selectActivePreviewPaneIndex");
117
+ function selectSavedNote(notesByTab, answers, tab) {
118
+ const note = notesByTab.get(tab) ?? answers.get(tab)?.notes;
119
+ return note && note.length > 0 ? note : void 0;
120
+ }
121
+ __name(selectSavedNote, "selectSavedNote");
122
+
123
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-copy.ts?ext=rpiv-ask-user-question
124
+ var HINT_PART_ENTER = "Enter to select";
125
+ var HINT_PART_NAV = "\u2191/\u2193 to navigate";
126
+ var HINT_PART_NEW_LINE = "Shift+Enter for newline";
127
+ var HINT_PART_CLEAR = "Ctrl+U to clear";
128
+ var HINT_PART_TOGGLE = "Space to toggle";
129
+ var HINT_PART_NOTES = "n to add notes";
130
+ var HINT_PART_TAB = "Tab to switch questions";
131
+ var HINT_PART_CANCEL = "Esc to cancel";
132
+ var KEY_PLACEHOLDER = "{key}";
133
+ var HINT_PART_COLLAPSE_TEMPLATE = `${KEY_PLACEHOLDER} to collapse`;
134
+ var HINT_PART_EXPAND_TEMPLATE = `${KEY_PLACEHOLDER} to expand`;
135
+ var HINT_PART_COLLAPSE = HINT_PART_COLLAPSE_TEMPLATE.replace(
136
+ KEY_PLACEHOLDER,
137
+ formatKeySpecForDisplay(DEFAULT_COLLAPSE_KEY)
138
+ );
139
+ var HINT_SINGLE = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HINT_PART_CANCEL].join(" \xB7 ");
140
+ var HINT_MULTI = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HINT_PART_TAB, HINT_PART_CANCEL].join(
141
+ " \xB7 "
142
+ );
143
+ var COLLAPSED_HINT_TEMPLATE = [HINT_PART_EXPAND_TEMPLATE, HINT_PART_CANCEL].join(" \xB7 ");
144
+ var REVIEW_HEADING = "Review your answers";
145
+ var READY_PROMPT = "Ready to submit your answers?";
146
+ var INCOMPLETE_WARNING_PREFIX = "\u26A0 Answer remaining questions before submitting:";
147
+
148
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts?ext=rpiv-ask-user-question
149
+ var NOTES_HEADER = "Notes:";
150
+ var GLOBAL_NOTES_HEADER = "Global note:";
151
+ var REVIEW_GLOBAL_HINT = "n to add a note";
152
+ var REVIEW_NOTE_LABEL = "Note";
153
+ var SAVED_NOTE_LABEL = "Note for this answer:";
154
+ var OneLineClippedText = class {
155
+ constructor(text, paddingLeft = 0) {
156
+ this.text = text;
157
+ this.paddingLeft = paddingLeft;
158
+ }
159
+ text;
160
+ paddingLeft;
161
+ static {
162
+ __name(this, "OneLineClippedText");
163
+ }
164
+ render(width) {
165
+ const pad = " ".repeat(this.paddingLeft);
166
+ const avail = Math.max(0, width - this.paddingLeft);
167
+ return [pad + truncateToWidth(this.text, avail, "\u2026", false)];
168
+ }
169
+ invalidate() {
170
+ }
171
+ handleInput(_data) {
172
+ }
173
+ };
174
+ var QuestionTabStrategy = class {
175
+ constructor(config) {
176
+ this.config = config;
177
+ }
178
+ config;
179
+ static {
180
+ __name(this, "QuestionTabStrategy");
181
+ }
182
+ /** Spacer(1) + OneLineClippedText(hint, 1) = 2 rendered rows. */
183
+ footerRowCount = 2;
184
+ headingRows(state) {
185
+ const out = [];
186
+ const question = this.config.questions[state.currentTab];
187
+ if (!this.config.isMulti && question?.header && question.header.length > 0) {
188
+ out.push(new Text(this.config.theme.bg("selectedBg", ` ${question.header} `), 1, 0));
189
+ out.push(new Spacer(1));
190
+ }
191
+ if (question) {
192
+ out.push(new Text(this.config.theme.bold(question.question), 1, 0));
193
+ out.push(new Spacer(1));
194
+ }
195
+ return out;
196
+ }
197
+ bodyComponent(state) {
198
+ const question = this.config.questions[state.currentTab];
199
+ const mso = this.config.tabsByIndex[state.currentTab]?.multiSelect;
200
+ if (question?.multiSelect === true && mso) return mso;
201
+ return this.config.getPreviewPane();
202
+ }
203
+ bodyHeight(width, _state) {
204
+ return this.config.getCurrentBodyHeight(width);
205
+ }
206
+ midRows(state) {
207
+ if (state.notesVisible) {
208
+ return [
209
+ new Text(this.config.theme.fg("muted", t("notes.header", NOTES_HEADER)), 1, 0),
210
+ this.config.notesInput,
211
+ new Spacer(1)
212
+ ];
213
+ }
214
+ const note = selectSavedNote(state.notesByTab, state.answers, state.currentTab);
215
+ if (note === void 0) return [];
216
+ const [firstLine = "", ...rest] = note.split("\n");
217
+ const text = `${t("notes.saved_label", SAVED_NOTE_LABEL)} ${firstLine}${rest.length > 0 ? " \u2026" : ""}`;
218
+ return [new OneLineClippedText(this.config.theme.fg("dim", text), 1)];
219
+ }
220
+ footerRows(state) {
221
+ const question = this.config.questions[state.currentTab];
222
+ return [
223
+ new Spacer(1),
224
+ new OneLineClippedText(
225
+ this.config.theme.fg("dim", buildHintText(question, this.config.isMulti, state, this.config.collapseKey)),
226
+ 1
227
+ )
228
+ ];
229
+ }
230
+ focusedItemRowRange(width, state) {
231
+ const question = this.config.questions[state.currentTab];
232
+ const mso = this.config.tabsByIndex[state.currentTab]?.multiSelect;
233
+ if (question?.multiSelect === true && mso) return mso.focusedItemRowRange(width);
234
+ return this.config.getPreviewPane().focusedItemRowRange(width);
235
+ }
236
+ };
237
+ var SubmitTabStrategy = class {
238
+ constructor(config) {
239
+ this.config = config;
240
+ }
241
+ config;
242
+ static {
243
+ __name(this, "SubmitTabStrategy");
244
+ }
245
+ /** Spacer(1) + Text(prompt, 1) + submitPicker(2) + OneLineClippedText(hint, 1) = 5 rendered rows. Fallback path lands at 5 via 2 Spacer(1)s in the picker slot. */
246
+ footerRowCount = 5;
247
+ headingRows(_state) {
248
+ return [
249
+ new Text(this.config.theme.bold(this.config.theme.fg("accent", t("review.heading", REVIEW_HEADING))), 1, 0),
250
+ new Spacer(1)
251
+ ];
252
+ }
253
+ bodyComponent(state) {
254
+ const c = new Container();
255
+ for (let i = 0; i < this.config.questions.length; i++) {
256
+ const q = this.config.questions[i];
257
+ const a = state.answers.get(i);
258
+ if (!a) continue;
259
+ const label = q.header && q.header.length > 0 ? q.header : `Q${i + 1}`;
260
+ const answerText = formatAnswerScalar(a, "summary");
261
+ c.addChild(new Text(this.config.theme.fg("muted", ` \u25CF ${label}`), 1, 0));
262
+ c.addChild(
263
+ new Text(` ${this.config.theme.fg("muted", "\u2192")} ${this.config.theme.fg("text", answerText)}`, 1, 0)
264
+ );
265
+ if (a.notes && a.notes.length > 0) {
266
+ c.addChild(
267
+ new Text(
268
+ this.config.theme.fg("dim", ` ${t("notes.saved_label", SAVED_NOTE_LABEL)} ${a.notes}`),
269
+ 1,
270
+ 0
271
+ )
272
+ );
273
+ }
274
+ }
275
+ const globalNote = state.notesByTab.get(this.config.questions.length);
276
+ if (!state.notesVisible && globalNote && globalNote.length > 0) {
277
+ c.addChild(new Text(this.config.theme.fg("muted", ` \u25CF ${t("review.note_label", REVIEW_NOTE_LABEL)}`), 1, 0));
278
+ c.addChild(
279
+ new Text(` ${this.config.theme.fg("muted", "\u2192")} ${this.config.theme.fg("text", globalNote)}`, 1, 0)
280
+ );
281
+ }
282
+ return c;
283
+ }
284
+ bodyHeight(width, state) {
285
+ return this.bodyComponent(state).render(width).length;
286
+ }
287
+ midRows(state) {
288
+ if (!state.notesVisible) return [];
289
+ return [
290
+ new Text(this.config.theme.fg("muted", t("notes.global_header", GLOBAL_NOTES_HEADER)), 1, 0),
291
+ this.config.notesInput,
292
+ new Spacer(1)
293
+ ];
294
+ }
295
+ footerRows(state) {
296
+ const missing = [];
297
+ for (let i = 0; i < this.config.questions.length; i++) {
298
+ const q = this.config.questions[i];
299
+ if (!state.answers.has(i)) {
300
+ missing.push(q.header && q.header.length > 0 ? q.header : `Q${i + 1}`);
301
+ }
302
+ }
303
+ const promptText = missing.length === 0 ? this.config.theme.fg("muted", t("review.ready", READY_PROMPT)) : this.config.theme.fg(
304
+ "warning",
305
+ `${t("review.incomplete", INCOMPLETE_WARNING_PREFIX)} ${missing.join(", ")}`
306
+ );
307
+ const out = [new Spacer(1), new OneLineClippedText(promptText, 1)];
308
+ if (this.config.submitPicker) {
309
+ out.push(this.config.submitPicker);
310
+ } else {
311
+ out.push(new Spacer(1));
312
+ out.push(new Spacer(1));
313
+ }
314
+ out.push(new OneLineClippedText(this.config.theme.fg("dim", buildSubmitHintText(state)), 1));
315
+ return out;
316
+ }
317
+ focusedItemRowRange(_width, _state) {
318
+ return void 0;
319
+ }
320
+ };
321
+ function buildHintText(question, isMulti, state, collapseKey) {
322
+ const parts = [t("hint.enter", HINT_PART_ENTER), t("hint.navigate", HINT_PART_NAV)];
323
+ if (question?.multiSelect === true) parts.push(t("hint.toggle", HINT_PART_TOGGLE));
324
+ if (question && !state.notesVisible && !state.inputMode) parts.push(t("hint.notes", HINT_PART_NOTES));
325
+ if (isMulti) parts.push(t("hint.tab", HINT_PART_TAB));
326
+ parts.push(t("hint.cancel", HINT_PART_CANCEL));
327
+ if (collapseKey !== COLLAPSE_KEY_OFF) {
328
+ parts.push(
329
+ t("hint.collapse", HINT_PART_COLLAPSE_TEMPLATE).replace(KEY_PLACEHOLDER, formatKeySpecForDisplay(collapseKey))
330
+ );
331
+ }
332
+ if (state.notesVisible || state.inputMode) parts.push(t("hint.newline", HINT_PART_NEW_LINE));
333
+ if (state.inputMode) parts.push(t("hint.clear", HINT_PART_CLEAR));
334
+ return parts.join(" \xB7 ");
335
+ }
336
+ __name(buildHintText, "buildHintText");
337
+ function buildSubmitHintText(state) {
338
+ const parts = [t("hint.enter", HINT_PART_ENTER), t("hint.navigate", HINT_PART_NAV)];
339
+ if (!state.notesVisible) parts.push(t("review.global_hint", REVIEW_GLOBAL_HINT));
340
+ parts.push(t("hint.cancel", HINT_PART_CANCEL));
341
+ if (state.notesVisible) parts.push(t("hint.newline", HINT_PART_NEW_LINE));
342
+ return parts.join(" \xB7 ");
343
+ }
344
+ __name(buildSubmitHintText, "buildSubmitHintText");
345
+
346
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts?ext=rpiv-ask-user-question
347
+ var MIN_SCROLL_ROWS_WITH_PINNED_HEADING = 3;
348
+ function renderFitsTerminal(natural, spacerRows) {
349
+ return spacerRows > 0 ? [...natural, ...Array(spacerRows).fill("")] : natural;
350
+ }
351
+ __name(renderFitsTerminal, "renderFitsTerminal");
352
+ function renderChromeOnly(natural, topFixed, bottomFixed, termRows) {
353
+ const chromeOnly = [...natural.slice(0, topFixed), ...natural.slice(natural.length - bottomFixed)];
354
+ return chromeOnly.length > termRows ? chromeOnly.slice(0, termRows) : chromeOnly;
355
+ }
356
+ __name(renderChromeOnly, "renderChromeOnly");
357
+ var DialogView = class {
358
+ static {
359
+ __name(this, "DialogView");
360
+ }
361
+ liveProps;
362
+ config;
363
+ questionStrategy;
364
+ submitStrategy;
365
+ maxFooterRowCount;
366
+ constructor(config, initialProps) {
367
+ this.config = config;
368
+ this.liveProps = initialProps;
369
+ this.questionStrategy = new QuestionTabStrategy({
370
+ theme: config.theme,
371
+ questions: config.questions,
372
+ getPreviewPane: /* @__PURE__ */ __name(() => this.liveProps.activePreviewPane, "getPreviewPane"),
373
+ tabsByIndex: config.tabsByIndex,
374
+ notesInput: config.notesInput,
375
+ isMulti: config.isMulti,
376
+ getCurrentBodyHeight: config.getCurrentBodyHeight,
377
+ collapseKey: config.collapseKey
378
+ });
379
+ this.submitStrategy = config.isMulti ? new SubmitTabStrategy({
380
+ theme: config.theme,
381
+ questions: config.questions,
382
+ submitPicker: config.submitPicker,
383
+ notesInput: config.notesInput
384
+ }) : void 0;
385
+ this.maxFooterRowCount = Math.max(this.questionStrategy.footerRowCount, this.submitStrategy?.footerRowCount ?? 0);
386
+ }
387
+ setProps(props) {
388
+ this.liveProps = props;
389
+ }
390
+ handleInput(_data) {
391
+ }
392
+ // Invalidation is driven by `QuestionnairePropsAdapter.invalidate()`, which
393
+ // owns the full set of renderables (binding registries + extras like
394
+ // `notesInput`). DialogView has no cached layout of its own.
395
+ invalidate() {
396
+ }
397
+ render(width) {
398
+ const state = this.liveProps.state;
399
+ const onSubmit = this.config.isMulti && state.currentTab === this.config.questions.length;
400
+ const strategy = onSubmit && this.submitStrategy ? this.submitStrategy : this.questionStrategy;
401
+ const headingRowCache = strategy.headingRows(state);
402
+ const headingHeight = headingRowCache.reduce((rows, c) => rows + c.render(width).length, 0);
403
+ const topFixed = 1 + (this.config.isMulti && this.config.tabBar ? 2 : 0) + 1;
404
+ const bottomFixed = 1 + strategy.footerRowCount;
405
+ const termRows = this.config.getTerminalRows();
406
+ this.allotBodyRows(
407
+ onSubmit,
408
+ termRows - topFixed - headingHeight - bottomFixed - this.rowsAroundBody(strategy, width)
409
+ );
410
+ const natural = this.buildContainerFromStrategy(strategy, headingRowCache).render(width);
411
+ const restingMidRows = state.notesVisible ? 0 : this.rowsAroundBody(strategy, width) - 1;
412
+ const spacerRows = Math.max(
413
+ 0,
414
+ this.config.getBodyHeight(width) + this.maxFooterRowCount - strategy.bodyHeight(width, state) - strategy.footerRowCount - restingMidRows
415
+ );
416
+ if (natural.length <= termRows) {
417
+ return renderFitsTerminal(natural, Math.min(spacerRows, termRows - natural.length));
418
+ }
419
+ const pinHeading = termRows - topFixed - headingHeight - bottomFixed >= MIN_SCROLL_ROWS_WITH_PINNED_HEADING;
420
+ const top = pinHeading ? topFixed + headingHeight : topFixed;
421
+ const middleRows = natural.length - top - bottomFixed;
422
+ const availableMiddle = Math.max(0, termRows - top - bottomFixed);
423
+ if (availableMiddle === 0) {
424
+ return renderChromeOnly(natural, top, bottomFixed, termRows);
425
+ }
426
+ const windowRows = Math.min(availableMiddle, middleRows);
427
+ const rowsBeforeBody = pinHeading ? 0 : headingHeight;
428
+ const focusedRange = strategy.focusedItemRowRange(width, state);
429
+ const scrollStart = computeScrollStart(focusedRange, rowsBeforeBody, windowRows, middleRows);
430
+ const scrollableMiddle = natural.slice(top + scrollStart, top + scrollStart + windowRows);
431
+ const focusedInWindow = focusedRange && [
432
+ rowsBeforeBody + focusedRange[0] - scrollStart,
433
+ rowsBeforeBody + focusedRange[1] - scrollStart
434
+ ];
435
+ decorateOverflow(
436
+ scrollableMiddle,
437
+ scrollStart > 0,
438
+ scrollStart + windowRows < middleRows,
439
+ this.config.theme,
440
+ focusedInWindow
441
+ );
442
+ const result = [...natural.slice(0, top), ...scrollableMiddle, ...natural.slice(natural.length - bottomFixed)];
443
+ return result.length > termRows ? result.slice(0, termRows) : result;
444
+ }
445
+ /** Rows between the body and the bottom border: the body's trailing Spacer(1) plus any mid rows (e.g. the notes editor). */
446
+ rowsAroundBody(strategy, width) {
447
+ return 1 + strategy.midRows(this.liveProps.state).reduce((rows, c) => rows + c.render(width).length, 0);
448
+ }
449
+ /**
450
+ * Hand the active question's preview pane the rows its body may use this frame, so a body
451
+ * taller than the space fits itself — options scroll in their column, the preview box
452
+ * shrinks but closes — instead of the scroll window below slicing through it (#210).
453
+ * Other tabs' panes are cleared so a stale allowance never skews the cross-tab height.
454
+ * Below `MIN_SCROLL_ROWS_WITH_PINNED_HEADING` the dialog's own scroll window takes over.
455
+ */
456
+ allotBodyRows(onSubmit, bodyRows) {
457
+ const active = onSubmit || bodyRows < MIN_SCROLL_ROWS_WITH_PINNED_HEADING ? void 0 : this.liveProps.activePreviewPane;
458
+ for (const tab of this.config.tabsByIndex) {
459
+ tab.preview?.setRowAllowance?.(tab.preview === active ? bodyRows : void 0);
460
+ }
461
+ }
462
+ buildContainerFromStrategy(strategy, headingRowCache) {
463
+ const { theme, isMulti, tabBar } = this.config;
464
+ const state = this.liveProps.state;
465
+ const container = new Container();
466
+ const border = /* @__PURE__ */ __name(() => new DynamicBorder((s) => theme.fg("accent", s)), "border");
467
+ container.addChild(border());
468
+ if (isMulti && tabBar) container.addChild(tabBar);
469
+ container.addChild(new Spacer(1));
470
+ for (const c of headingRowCache) container.addChild(c);
471
+ container.addChild(strategy.bodyComponent(state));
472
+ container.addChild(new Spacer(1));
473
+ for (const c of strategy.midRows(state)) container.addChild(c);
474
+ container.addChild(border());
475
+ for (const c of strategy.footerRows(state)) container.addChild(c);
476
+ return container;
477
+ }
478
+ };
479
+
480
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/component-binding.ts?ext=rpiv-ask-user-question
481
+ function globalBinding(spec) {
482
+ return {
483
+ apply: /* @__PURE__ */ __name((state, ctx) => spec.component.setProps(spec.select(state, ctx)), "apply"),
484
+ invalidate: /* @__PURE__ */ __name(() => spec.component.invalidate(), "invalidate")
485
+ };
486
+ }
487
+ __name(globalBinding, "globalBinding");
488
+ function perTabBinding(spec) {
489
+ return {
490
+ apply: /* @__PURE__ */ __name((state, ctx) => {
491
+ if (spec.predicate && !spec.predicate(state, ctx)) return;
492
+ spec.resolve(ctx.tab)?.setProps(spec.select(state, ctx));
493
+ }, "apply")
494
+ };
495
+ }
496
+ __name(perTabBinding, "perTabBinding");
497
+
498
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/inline-input.ts?ext=rpiv-ask-user-question
499
+ var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
500
+ function resolveCursorOffset(buffer, requested) {
501
+ if (requested !== void 0 && requested >= 0 && requested <= buffer.length) return requested;
502
+ return buffer.length;
503
+ }
504
+ __name(resolveCursorOffset, "resolveCursorOffset");
505
+ function buildCursorRaw(buffer, offset) {
506
+ const before = buffer.slice(0, offset);
507
+ const [firstGrapheme] = graphemeSegmenter.segment(buffer.slice(offset));
508
+ const rawAt = firstGrapheme ? firstGrapheme.segment : "";
509
+ const cursorAtLineEnd = rawAt === "\n";
510
+ const atCursor = rawAt === "" || rawAt === " " || cursorAtLineEnd ? "\xA0" : rawAt;
511
+ const after = buffer.slice(offset + (cursorAtLineEnd ? 0 : rawAt.length));
512
+ return `${before}${CURSOR_MARKER}\x1B[7m${atCursor}\x1B[27m${after}`;
513
+ }
514
+ __name(buildCursorRaw, "buildCursorRaw");
515
+ function renderInlineInputRow(opts) {
516
+ const { buffer, cursorOffset, rowPrefix, continuationPrefix, contentWidth, selectedText } = opts;
517
+ const raw = buildCursorRaw(buffer, resolveCursorOffset(buffer, cursorOffset));
518
+ return wrapTextWithAnsi(raw, contentWidth).map((segment, index) => {
519
+ const prefix = index === 0 ? rowPrefix : continuationPrefix;
520
+ return selectedText(`${prefix}${segment}`);
521
+ });
522
+ }
523
+ __name(renderInlineInputRow, "renderInlineInputRow");
524
+
525
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts?ext=rpiv-ask-user-question
526
+ var ACTIVE_POINTER = "\u276F ";
527
+ var INACTIVE_POINTER = " ";
528
+ var CHECKED = "[\u2714]";
529
+ var UNCHECKED = "[ ]";
530
+ var NUMBER_SEPARATOR = ". ";
531
+ var BOX_LABEL_GAP = " ";
532
+ var CONTINUATION_INDENT = " ";
533
+ var MULTI_SUBMIT_LABEL = "Submit";
534
+ var MultiSelectView = class {
535
+ constructor(theme, question) {
536
+ this.theme = theme;
537
+ this.question = question;
538
+ this.props = {
539
+ rows: [],
540
+ other: { active: false, checked: false, inputMode: false, inputBuffer: "", inputCursorOffset: void 0 },
541
+ nextActive: false,
542
+ nextLabel: displayLabel("next")
543
+ };
544
+ }
545
+ theme;
546
+ question;
547
+ static {
548
+ __name(this, "MultiSelectView");
549
+ }
550
+ props;
551
+ cachedLayout;
552
+ setProps(props) {
553
+ this.props = props;
554
+ this.cachedLayout = void 0;
555
+ }
556
+ handleInput(_data) {
557
+ }
558
+ invalidate() {
559
+ this.cachedLayout = void 0;
560
+ }
561
+ render(width) {
562
+ return this.layout(width).lines;
563
+ }
564
+ focusedItemRowRange(width) {
565
+ return this.layout(width).focusedRange;
566
+ }
567
+ naturalHeight(width) {
568
+ return this.layout(width).lines.length;
569
+ }
570
+ layout(width) {
571
+ if (this.cachedLayout?.width === width) return this.cachedLayout.value;
572
+ const build = { lines: [], focusedRange: [0, 0] };
573
+ const contentWidth = Math.max(1, width - this.prefixVisibleWidth());
574
+ const numberWidth = String(Math.max(1, this.question.options.length + 1)).length;
575
+ this.appendOptionRows(build, width, contentWidth, numberWidth);
576
+ const otherStart = build.lines.length;
577
+ build.lines.push(...this.renderOtherRow(contentWidth, numberWidth));
578
+ if (this.props.other.active) build.focusedRange = [otherStart, build.lines.length];
579
+ this.appendNextRow(build, width);
580
+ const value = { lines: build.lines, focusedRange: build.focusedRange };
581
+ this.cachedLayout = { width, value };
582
+ return value;
583
+ }
584
+ appendOptionRows(build, width, contentWidth, numberWidth) {
585
+ for (let i = 0; i < this.question.options.length; i++) {
586
+ const opt = this.question.options[i];
587
+ const row = this.props.rows[i];
588
+ if (!opt || !row) continue;
589
+ const start = build.lines.length;
590
+ const pointer = row.active ? this.theme.fg("accent", ACTIVE_POINTER) : INACTIVE_POINTER;
591
+ const box = row.checked ? this.theme.fg("accent", CHECKED) : this.theme.fg("muted", UNCHECKED);
592
+ const label = truncateToWidth(opt.label, contentWidth, "\u2026");
593
+ const styledLabel = row.active ? this.theme.fg("accent", this.theme.bold(label)) : label;
594
+ const number = String(i + 1).padStart(numberWidth, " ");
595
+ build.lines.push(
596
+ truncateToWidth(`${pointer}${number}${NUMBER_SEPARATOR}${box}${BOX_LABEL_GAP}${styledLabel}`, width, "")
597
+ );
598
+ if (opt.description) {
599
+ for (const segment of wrapTextWithAnsi(opt.description, contentWidth)) {
600
+ build.lines.push(CONTINUATION_INDENT + this.theme.fg("muted", segment));
601
+ }
602
+ }
603
+ if (row.active) build.focusedRange = [start, build.lines.length];
604
+ }
605
+ }
606
+ appendNextRow(build, width) {
607
+ const nextStart = build.lines.length;
608
+ const nextPointer = this.props.nextActive ? this.theme.fg("accent", ACTIVE_POINTER) : INACTIVE_POINTER;
609
+ const nextLabel = this.props.nextActive ? this.theme.fg("accent", this.theme.bold(this.props.nextLabel)) : this.props.nextLabel;
610
+ build.lines.push(truncateToWidth(`${nextPointer}${nextLabel}`, width, ""));
611
+ if (this.props.nextActive) build.focusedRange = [nextStart, build.lines.length];
612
+ }
613
+ renderOtherRow(contentWidth, numberWidth) {
614
+ const other = this.props.other;
615
+ const pointer = other.active ? this.theme.fg("accent", ACTIVE_POINTER) : INACTIVE_POINTER;
616
+ const box = other.checked ? this.theme.fg("accent", CHECKED) : this.theme.fg("muted", UNCHECKED);
617
+ const number = String(this.question.options.length + 1).padStart(numberWidth, " ");
618
+ const rowPrefix = `${pointer}${number}${NUMBER_SEPARATOR}${box}${BOX_LABEL_GAP}`;
619
+ const continuationPrefix = " ".repeat(visibleWidth(rowPrefix));
620
+ const selectedText = /* @__PURE__ */ __name((text) => this.theme.fg("accent", this.theme.bold(text)), "selectedText");
621
+ if (other.active && other.inputMode) {
622
+ return renderInlineInputRow({
623
+ buffer: other.inputBuffer,
624
+ cursorOffset: other.inputCursorOffset,
625
+ rowPrefix,
626
+ continuationPrefix,
627
+ contentWidth,
628
+ selectedText
629
+ });
630
+ }
631
+ return wrapTextWithAnsi(other.inputBuffer || displayLabel("other"), contentWidth).map((segment, index) => {
632
+ const line = `${index === 0 ? rowPrefix : continuationPrefix}${segment}`;
633
+ return other.active ? selectedText(line) : line;
634
+ });
635
+ }
636
+ prefixVisibleWidth() {
637
+ const numberWidth = String(Math.max(1, this.question.options.length + 1)).length;
638
+ return visibleWidth(INACTIVE_POINTER) + numberWidth + visibleWidth(`${NUMBER_SEPARATOR}${UNCHECKED}${BOX_LABEL_GAP}`);
639
+ }
640
+ };
641
+
642
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts?ext=rpiv-ask-user-question
643
+ var WrappingSelect = class _WrappingSelect {
644
+ static {
645
+ __name(this, "WrappingSelect");
646
+ }
647
+ static ACTIVE_POINTER = "\u276F ";
648
+ static INACTIVE_POINTER = " ";
649
+ static NUMBER_SEPARATOR = ". ";
650
+ static CONFIRMED_MARK = " \u2714";
651
+ static MIN_CONTENT_WIDTH = 1;
652
+ items;
653
+ maxVisible;
654
+ theme;
655
+ numberStartOffset;
656
+ totalItemsForNumbering;
657
+ selectedIndex = 0;
658
+ focused = true;
659
+ inputBuffer = "";
660
+ inputCursorOffset = void 0;
661
+ /**
662
+ * Index of the row that was previously confirmed for this list (e.g. the user's prior
663
+ * answer when re-entering a multi-question tab). Renders `<label> ✔` in the active-row
664
+ * styling but WITHOUT the `❯` pointer — pointer is reserved for the live cursor. When
665
+ * `selectedIndex === confirmedIndex && focused`, the active rendering wins (no double-mark).
666
+ */
667
+ confirmedIndex = void 0;
668
+ /**
669
+ * When set together with `confirmedIndex`, replaces the row's static label at render time.
670
+ * Used for the `kind: "other"` sentinel — its label is "Type something." but if the user's
671
+ * prior answer was custom text, we render that text instead (e.g. `4. Hello ✔`).
672
+ */
673
+ confirmedLabelOverride = void 0;
674
+ constructor(items, maxVisible, theme, options = {}) {
675
+ this.items = items;
676
+ this.maxVisible = Math.max(1, maxVisible);
677
+ this.theme = theme;
678
+ this.numberStartOffset = options.numberStartOffset ?? 0;
679
+ this.totalItemsForNumbering = options.totalItemsForNumbering ?? items.length;
680
+ }
681
+ /**
682
+ * Update the numbering offset + total padding width without rebuilding the component.
683
+ * Lets the host realign the number column when the underlying item set changes.
684
+ */
685
+ setNumbering(numberStartOffset, totalItemsForNumbering) {
686
+ this.numberStartOffset = numberStartOffset;
687
+ this.totalItemsForNumbering = Math.max(1, totalItemsForNumbering);
688
+ }
689
+ setSelectedIndex(index) {
690
+ this.selectedIndex = Math.max(0, Math.min(index, this.items.length - 1));
691
+ }
692
+ setFocused(focused) {
693
+ this.focused = focused;
694
+ }
695
+ /**
696
+ * Mark a previously-confirmed row. Pass `undefined` to clear. `labelOverride` replaces
697
+ * the row's static `item.label` at render time — used for the `kind: "other"` sentinel so
698
+ * the row reads `Hello ✔` instead of `Type something. ✔` when the prior answer was custom
699
+ * text.
700
+ */
701
+ setConfirmedIndex(index, labelOverride) {
702
+ if (index === void 0) {
703
+ this.confirmedIndex = void 0;
704
+ this.confirmedLabelOverride = void 0;
705
+ return;
706
+ }
707
+ this.confirmedIndex = Math.max(0, Math.min(index, this.items.length - 1));
708
+ this.confirmedLabelOverride = labelOverride;
709
+ }
710
+ setInputBuffer(text) {
711
+ this.inputBuffer = text;
712
+ }
713
+ /** Set the cursor offset for the inline input row. `undefined` → end-of-buffer fallback. */
714
+ setInputCursorOffset(offset) {
715
+ this.inputCursorOffset = offset;
716
+ }
717
+ /** Intentionally empty — input is routed at the container level. */
718
+ handleInput(_data) {
719
+ }
720
+ invalidate() {
721
+ }
722
+ render(width) {
723
+ if (this.items.length === 0) return [];
724
+ const { startIndex, endIndex } = this.computeVisibleWindow();
725
+ const numberWidth = String(Math.max(1, this.totalItemsForNumbering)).length;
726
+ const lines = [];
727
+ for (let i = startIndex; i < endIndex; i++) {
728
+ const item = this.items[i];
729
+ if (!item) continue;
730
+ const isActive = i === this.selectedIndex && this.focused;
731
+ lines.push(...this.renderItem(item, i, isActive, width, numberWidth));
732
+ }
733
+ if (this.hasItemsOutsideWindow(startIndex, endIndex)) {
734
+ lines.push(this.theme.scrollInfo(` (${this.selectedIndex + 1}/${this.items.length})`));
735
+ }
736
+ return lines;
737
+ }
738
+ /**
739
+ * Returns the [startRow, endRow) range of the focused (selected) item within
740
+ * the output of `render(width)`. Computed by iterating the visible window and
741
+ * summing per-item row counts — O(maxVisible) per call.
742
+ */
743
+ focusedItemRowRange(width) {
744
+ if (this.items.length === 0) return [0, 0];
745
+ const { startIndex, endIndex } = this.computeVisibleWindow();
746
+ const numberWidth = String(Math.max(1, this.totalItemsForNumbering)).length;
747
+ let row = 0;
748
+ for (let i = startIndex; i < endIndex; i++) {
749
+ const item = this.items[i];
750
+ if (!item) continue;
751
+ const isActive = i === this.selectedIndex && this.focused;
752
+ const itemRowCount = this.computeItemRowCount(item, i, isActive, width, numberWidth);
753
+ if (i === this.selectedIndex) {
754
+ return [row, row + itemRowCount];
755
+ }
756
+ row += itemRowCount;
757
+ }
758
+ return [0, 1];
759
+ }
760
+ /**
761
+ * Per-item row count. Delegates to `renderItem().length` so `renderItem` remains
762
+ * the single source of truth for per-item row math — eliminates the prior shadow-copy
763
+ * that risked silent miscounts when new `kind` values branch in `renderItem` but not here.
764
+ */
765
+ computeItemRowCount(item, index, isActive, width, numberWidth) {
766
+ return this.renderItem(item, index, isActive, width, numberWidth).length;
767
+ }
768
+ computeVisibleWindow() {
769
+ const half = Math.floor(this.maxVisible / 2);
770
+ const startIndex = Math.max(0, Math.min(this.selectedIndex - half, this.items.length - this.maxVisible));
771
+ const endIndex = Math.min(startIndex + this.maxVisible, this.items.length);
772
+ return { startIndex, endIndex };
773
+ }
774
+ hasItemsOutsideWindow(startIndex, endIndex) {
775
+ return startIndex > 0 || endIndex < this.items.length;
776
+ }
777
+ renderItem(item, index, isActive, width, numberWidth) {
778
+ const rowPrefix = this.buildRowPrefix(index, isActive, numberWidth);
779
+ const continuationPrefix = " ".repeat(visibleWidth(rowPrefix));
780
+ const contentWidth = Math.max(_WrappingSelect.MIN_CONTENT_WIDTH, width - visibleWidth(rowPrefix));
781
+ if (this.shouldRenderAsInlineInput(item, isActive)) {
782
+ return this.renderInlineInputRow(rowPrefix, continuationPrefix, contentWidth);
783
+ }
784
+ const { label, isConfirmed } = this.deriveConfirmedState(item, index);
785
+ const applySelectedStyle = isActive || isConfirmed;
786
+ return [
787
+ ...this.renderLabelBlock(label, rowPrefix, continuationPrefix, contentWidth, applySelectedStyle),
788
+ ...this.renderDescriptionBlock(item.description, continuationPrefix, contentWidth)
789
+ ];
790
+ }
791
+ /**
792
+ * Derive the confirmed-rendering facts for one row: the final label (custom draft
793
+ * or confirmed-override aware) and whether the ✔ mark applies.
794
+ */
795
+ deriveConfirmedState(item, index) {
796
+ const customDraft = item.kind === "other" ? this.inputBuffer : void 0;
797
+ const customDraftDiffersFromConfirmed = item.kind === "other" && customDraft !== "" && index === this.confirmedIndex && customDraft !== (this.confirmedLabelOverride ?? "");
798
+ const isConfirmed = index === this.confirmedIndex && !customDraftDiffersFromConfirmed;
799
+ const baseLabel = customDraft ? customDraft : item.label;
800
+ const label = isConfirmed ? `${this.confirmedLabelOverride ?? baseLabel}${_WrappingSelect.CONFIRMED_MARK}` : baseLabel;
801
+ return { label, isConfirmed };
802
+ }
803
+ buildRowPrefix(index, isActive, numberWidth) {
804
+ const pointer = isActive ? _WrappingSelect.ACTIVE_POINTER : _WrappingSelect.INACTIVE_POINTER;
805
+ const displayNumber = this.numberStartOffset + index + 1;
806
+ const paddedNumber = String(displayNumber).padStart(numberWidth, " ");
807
+ return `${pointer}${paddedNumber}${_WrappingSelect.NUMBER_SEPARATOR}`;
808
+ }
809
+ shouldRenderAsInlineInput(item, isActive) {
810
+ return item.kind === "other" && isActive;
811
+ }
812
+ /** Render the inline editor across logical and visually wrapped lines. */
813
+ renderInlineInputRow(rowPrefix, continuationPrefix, contentWidth) {
814
+ return renderInlineInputRow({
815
+ buffer: this.inputBuffer,
816
+ cursorOffset: this.inputCursorOffset,
817
+ rowPrefix,
818
+ continuationPrefix,
819
+ contentWidth,
820
+ selectedText: this.theme.selectedText
821
+ });
822
+ }
823
+ renderLabelBlock(label, rowPrefix, continuationPrefix, contentWidth, applySelectedStyle) {
824
+ const wrapped = wrapTextWithAnsi(label, contentWidth);
825
+ return wrapped.map((segment, index) => {
826
+ const prefix = index === 0 ? rowPrefix : continuationPrefix;
827
+ const line = `${prefix}${segment}`;
828
+ return applySelectedStyle ? this.theme.selectedText(line) : line;
829
+ });
830
+ }
831
+ renderDescriptionBlock(description, continuationPrefix, contentWidth) {
832
+ if (!description) return [];
833
+ const wrapped = wrapTextWithAnsi(description, contentWidth);
834
+ return wrapped.map((segment) => `${continuationPrefix}${this.theme.description(segment)}`);
835
+ }
836
+ };
837
+
838
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/option-list-view.ts?ext=rpiv-ask-user-question
839
+ var MAX_VISIBLE_OPTIONS = 10;
840
+ var OptionListView = class {
841
+ static {
842
+ __name(this, "OptionListView");
843
+ }
844
+ select;
845
+ constructor(config) {
846
+ this.select = new WrappingSelect(config.items, Math.min(config.items.length, MAX_VISIBLE_OPTIONS), config.theme, {
847
+ numberStartOffset: 0,
848
+ totalItemsForNumbering: config.items.length
849
+ });
850
+ }
851
+ setProps(props) {
852
+ this.select.setSelectedIndex(props.selectedIndex);
853
+ this.select.setFocused(props.focused);
854
+ this.select.setConfirmedIndex(props.confirmed?.index, props.confirmed?.labelOverride);
855
+ this.select.setInputBuffer(props.inputBuffer);
856
+ this.select.setInputCursorOffset(props.inputCursorOffset);
857
+ }
858
+ handleInput(_data) {
859
+ }
860
+ invalidate() {
861
+ this.select.invalidate();
862
+ }
863
+ render(width) {
864
+ return this.select.render(width);
865
+ }
866
+ focusedItemRowRange(width) {
867
+ return this.select.focusedItemRowRange(width);
868
+ }
869
+ };
870
+
871
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-box-renderer.ts?ext=rpiv-ask-user-question
872
+ var ANSI_SGR_RE = /\x1b\[[0-9;]*m/g;
873
+ var ANSI_OSC8_RE = /\x1b\]8;[^\x07\x1b]*(?:\x07|\x1b\\)/g;
874
+ var FENCE_MARKER_RE = /^`{3}/;
875
+ var BORDER_VERTICAL_OVERHEAD = 2;
876
+ var BORDER_HORIZONTAL_OVERHEAD = 2;
877
+ var BORDER_INNER_PADDING_HORIZONTAL = 1;
878
+ var BOX_MIN_CONTENT_WIDTH = 40;
879
+ function stripFenceMarkers(lines) {
880
+ return lines.filter((line) => {
881
+ const clean = line.replace(ANSI_SGR_RE, "").replace(ANSI_OSC8_RE, "");
882
+ return !FENCE_MARKER_RE.test(clean);
883
+ });
884
+ }
885
+ __name(stripFenceMarkers, "stripFenceMarkers");
886
+ function renderBorderedBox(lines, width, colorFn, hidden = 0) {
887
+ const dashSpan = Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD);
888
+ const contentInner = Math.max(1, dashSpan - 2 * BORDER_INNER_PADDING_HORIZONTAL);
889
+ const pad = " ".repeat(BORDER_INNER_PADDING_HORIZONTAL);
890
+ const top = colorFn(`\u250C${"\u2500".repeat(dashSpan)}\u2510`);
891
+ const out = [top];
892
+ for (const line of lines) {
893
+ const padded = truncateToWidth(line, contentInner, "", true);
894
+ out.push(`${colorFn("\u2502")}${pad}${padded}${pad}${colorFn("\u2502")}`);
895
+ }
896
+ if (hidden > 0) {
897
+ const indicator = ` \u2702 \u2500\u2500 ${hidden} lines hidden \u2500\u2500 `;
898
+ const space = dashSpan - indicator.length;
899
+ const leftFill = "\u2500".repeat(Math.max(0, Math.floor(space / 2)));
900
+ const rightFill = "\u2500".repeat(Math.max(0, dashSpan - leftFill.length - indicator.length));
901
+ out.push(colorFn(`\u2514${leftFill}${indicator}${rightFill}\u2518`));
902
+ } else {
903
+ out.push(colorFn(`\u2514${"\u2500".repeat(dashSpan)}\u2518`));
904
+ }
905
+ return out;
906
+ }
907
+ __name(renderBorderedBox, "renderBorderedBox");
908
+ function computeBoxDimensions(contentLines, maxInnerWidth) {
909
+ let widest = Math.min(BOX_MIN_CONTENT_WIDTH, maxInnerWidth);
910
+ for (const line of contentLines) {
911
+ const w = visibleWidth(line.replace(/\s+$/, ""));
912
+ if (w > widest) widest = w;
913
+ }
914
+ const innerWidth = Math.min(widest, maxInnerWidth);
915
+ const boxWidth = innerWidth + BORDER_HORIZONTAL_OVERHEAD + 2 * BORDER_INNER_PADDING_HORIZONTAL;
916
+ return { innerWidth, boxWidth };
917
+ }
918
+ __name(computeBoxDimensions, "computeBoxDimensions");
919
+
920
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/markdown-content-cache.ts?ext=rpiv-ask-user-question
921
+ var MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE = 20;
922
+ var MAX_PREVIEW_HEIGHT_STACKED = 15;
923
+ var NO_PREVIEW_TEXT = "No preview available";
924
+ var NOTES_AFFORDANCE_OVERHEAD = 2;
925
+ var MarkdownContentCache = class {
926
+ static {
927
+ __name(this, "MarkdownContentCache");
928
+ }
929
+ previewTexts;
930
+ markdownCache;
931
+ cachedWidth;
932
+ theme;
933
+ markdownTheme;
934
+ constructor(question, theme, markdownTheme) {
935
+ this.theme = theme;
936
+ this.markdownTheme = markdownTheme;
937
+ this.previewTexts = /* @__PURE__ */ new Map();
938
+ for (let i = 0; i < question.options.length; i++) {
939
+ const raw = question.options[i]?.preview;
940
+ if (raw && raw.length > 0) this.previewTexts.set(i, raw);
941
+ }
942
+ this.markdownCache = /* @__PURE__ */ new Map();
943
+ }
944
+ hasAnyPreview() {
945
+ return this.previewTexts.size > 0;
946
+ }
947
+ has(optionIndex) {
948
+ return this.previewTexts.has(optionIndex);
949
+ }
950
+ /**
951
+ * Compute the body lines for a given option at a given inner width. Width changes
952
+ * invalidate the per-Markdown render cache.
953
+ */
954
+ bodyFor(optionIndex, innerWidth) {
955
+ if (this.cachedWidth !== innerWidth) {
956
+ for (const md2 of this.markdownCache.values()) md2.invalidate();
957
+ this.cachedWidth = innerWidth;
958
+ }
959
+ const text = this.previewTexts.get(optionIndex);
960
+ if (!text) {
961
+ const placeholder = this.theme.fg("dim", t("preview.no_preview", NO_PREVIEW_TEXT));
962
+ const pad = Math.max(0, innerWidth - visibleWidth(placeholder));
963
+ return [placeholder + " ".repeat(pad)];
964
+ }
965
+ let md = this.markdownCache.get(optionIndex);
966
+ if (!md) {
967
+ md = new Markdown(text, 0, 0, this.markdownTheme);
968
+ this.markdownCache.set(optionIndex, md);
969
+ }
970
+ return stripFenceMarkers(md.render(innerWidth));
971
+ }
972
+ invalidate() {
973
+ for (const md of this.markdownCache.values()) md.invalidate();
974
+ this.cachedWidth = void 0;
975
+ }
976
+ };
977
+
978
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts?ext=rpiv-ask-user-question
979
+ var NOTES_AFFORDANCE_TEXT = "Notes: press n to add notes";
980
+ var NOTES_EDIT_AFFORDANCE_TEXT = "Note saved: press n to edit";
981
+ function affordanceText(affordance) {
982
+ return affordance === "edit" ? t("preview.notes_edit_affordance", NOTES_EDIT_AFFORDANCE_TEXT) : t("preview.notes_affordance", NOTES_AFFORDANCE_TEXT);
983
+ }
984
+ __name(affordanceText, "affordanceText");
985
+ function contentBudgetFor(mode) {
986
+ const cap = mode === "side-by-side" ? MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE : MAX_PREVIEW_HEIGHT_STACKED;
987
+ return Math.max(1, cap - BORDER_VERTICAL_OVERHEAD - NOTES_AFFORDANCE_OVERHEAD);
988
+ }
989
+ __name(contentBudgetFor, "contentBudgetFor");
990
+ function fitBlock(rawRows, mode, maxRows) {
991
+ const contentRows = Math.min(rawRows, contentBudgetFor(mode));
992
+ if (maxRows === void 0 || BORDER_VERTICAL_OVERHEAD + contentRows + NOTES_AFFORDANCE_OVERHEAD <= maxRows) {
993
+ return { contentRows, affordance: true };
994
+ }
995
+ const fitted = Math.min(contentRows, maxRows - BORDER_VERTICAL_OVERHEAD);
996
+ return fitted >= 1 ? { contentRows: fitted, affordance: false } : void 0;
997
+ }
998
+ __name(fitBlock, "fitBlock");
999
+ function innerWidthFor(width) {
1000
+ return Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD - 2 * BORDER_INNER_PADDING_HORIZONTAL);
1001
+ }
1002
+ __name(innerWidthFor, "innerWidthFor");
1003
+ var PreviewBlockRenderer = class {
1004
+ static {
1005
+ __name(this, "PreviewBlockRenderer");
1006
+ }
1007
+ theme;
1008
+ cache;
1009
+ constructor(config) {
1010
+ this.theme = config.theme;
1011
+ this.cache = new MarkdownContentCache(config.question, config.theme, config.markdownTheme);
1012
+ }
1013
+ hasAnyPreview() {
1014
+ return this.cache.hasAnyPreview();
1015
+ }
1016
+ has(optionIndex) {
1017
+ return this.cache.has(optionIndex);
1018
+ }
1019
+ invalidate() {
1020
+ this.cache.invalidate();
1021
+ }
1022
+ /**
1023
+ * Height contribution of the preview block — always equal to `renderBlock(...).length`
1024
+ * for the same arguments (the affordance overhead is not gated by the `NotesAffordance`).
1025
+ * `maxRows` is the row allowance the dialog can give the block; see `fitBlock`.
1026
+ */
1027
+ blockHeight(width, optionIndex, mode, maxRows) {
1028
+ const rawRows = this.cache.bodyFor(optionIndex, innerWidthFor(width)).length;
1029
+ const fit = fitBlock(rawRows, mode, maxRows);
1030
+ return fit ? BORDER_VERTICAL_OVERHEAD + fit.contentRows + (fit.affordance ? NOTES_AFFORDANCE_OVERHEAD : 0) : 0;
1031
+ }
1032
+ /**
1033
+ * Render the preview block at `width`: bordered box, then (when it fits) a blank separator
1034
+ * and the notes-affordance row. `affordance` picks the text (`none` blanks it), shown only when
1035
+ * the option carries a preview; the row is emitted (empty when gated) whenever the block keeps
1036
+ * it, so the row count only depends on the fit. Under a `maxRows` allowance the box always closes and its bottom border
1037
+ * counts the lines left out; with no room for a box the block is empty.
1038
+ */
1039
+ renderBlock(width, optionIndex, mode, affordance, maxRows) {
1040
+ const maxInnerWidth = innerWidthFor(width);
1041
+ const raw = this.cache.bodyFor(optionIndex, maxInnerWidth);
1042
+ const fit = fitBlock(raw.length, mode, maxRows);
1043
+ if (!fit) return [];
1044
+ const hidden = raw.length - fit.contentRows;
1045
+ const contentLines = hidden > 0 ? raw.slice(0, fit.contentRows) : raw;
1046
+ const { boxWidth } = computeBoxDimensions(contentLines, maxInnerWidth);
1047
+ const colorFn = /* @__PURE__ */ __name((s) => this.theme.fg("accent", s), "colorFn");
1048
+ const boxedLines = renderBorderedBox(contentLines, boxWidth, colorFn, hidden);
1049
+ if (!fit.affordance) return boxedLines;
1050
+ const text = affordance !== "none" && this.cache.has(optionIndex) ? affordanceText(affordance) : "";
1051
+ return [...boxedLines, "", text ? this.theme.fg("muted", text) : ""];
1052
+ }
1053
+ };
1054
+
1055
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-layout-decider.ts?ext=rpiv-ask-user-question
1056
+ var PREVIEW_MIN_WIDTH = 100;
1057
+ var PREVIEW_COLUMN_GAP = 2;
1058
+ var PREVIEW_PADDING_LEFT = 1;
1059
+ var STACKED_GAP_ROWS = 1;
1060
+ var MIN_LEFT = 30;
1061
+ var MAX_LEFT_RATIO = 0.5;
1062
+ var MIN_PREVIEW_WIDTH = 45;
1063
+ var CONFIRMED_OVERHEAD = 2;
1064
+ function decideLayout(terminalWidth, paneWidth) {
1065
+ return terminalWidth >= PREVIEW_MIN_WIDTH && paneWidth >= PREVIEW_MIN_WIDTH ? "side-by-side" : "stacked";
1066
+ }
1067
+ __name(decideLayout, "decideLayout");
1068
+ function adaptiveLeftWidth(items, totalForNumbering, paneWidth) {
1069
+ const prefixW = String(Math.max(1, totalForNumbering)).length + 4;
1070
+ const confirmedOverhead = CONFIRMED_OVERHEAD;
1071
+ let maxLabel = 0;
1072
+ for (const item of items) {
1073
+ const w = visibleWidth(item.label);
1074
+ if (w > maxLabel) maxLabel = w;
1075
+ }
1076
+ const desired = maxLabel + prefixW + confirmedOverhead;
1077
+ const ratioCapped = Math.min(desired, Math.floor(paneWidth * MAX_LEFT_RATIO));
1078
+ const available = paneWidth - PREVIEW_COLUMN_GAP - MIN_PREVIEW_WIDTH;
1079
+ return Math.max(MIN_LEFT, Math.min(ratioCapped, Math.max(1, available)));
1080
+ }
1081
+ __name(adaptiveLeftWidth, "adaptiveLeftWidth");
1082
+ function crossTabMaxLeftWidth(tabs, itemsByTab, paneWidth) {
1083
+ let max = MIN_LEFT;
1084
+ for (let i = 0; i < tabs.length; i++) {
1085
+ const items = itemsByTab[i] ?? [];
1086
+ const totalForNumbering = items.length;
1087
+ const tabWidth = adaptiveLeftWidth(items, totalForNumbering, paneWidth);
1088
+ if (tabWidth > max) max = tabWidth;
1089
+ }
1090
+ return max;
1091
+ }
1092
+ __name(crossTabMaxLeftWidth, "crossTabMaxLeftWidth");
1093
+ function previewSourceWidth(question) {
1094
+ let max = 0;
1095
+ for (const option of question.options) {
1096
+ const text = option.preview;
1097
+ if (!text) continue;
1098
+ for (const line of text.split("\n")) {
1099
+ const w = visibleWidth(line);
1100
+ if (w > max) max = w;
1101
+ }
1102
+ }
1103
+ return max;
1104
+ }
1105
+ __name(previewSourceWidth, "previewSourceWidth");
1106
+ function crossTabPreviewBudget(questions, paneWidth) {
1107
+ let max = MIN_PREVIEW_WIDTH;
1108
+ for (const question of questions) {
1109
+ const rawWidth = previewSourceWidth(question);
1110
+ const capped = Math.min(rawWidth, paneWidth - PREVIEW_COLUMN_GAP - MIN_LEFT);
1111
+ const budget = capped + BORDER_HORIZONTAL_OVERHEAD + 2 * BORDER_INNER_PADDING_HORIZONTAL + PREVIEW_PADDING_LEFT;
1112
+ if (budget > max) max = budget;
1113
+ }
1114
+ return max;
1115
+ }
1116
+ __name(crossTabPreviewBudget, "crossTabPreviewBudget");
1117
+ function crossTabLeftWidthWithDonation(tabs, itemsByTab, questions, paneWidth) {
1118
+ const labelDriven = crossTabMaxLeftWidth(tabs, itemsByTab, paneWidth);
1119
+ const previewBudget = crossTabPreviewBudget(questions, paneWidth);
1120
+ const slackDonation = paneWidth - PREVIEW_COLUMN_GAP - previewBudget;
1121
+ const previewSafetyCeiling = paneWidth - PREVIEW_COLUMN_GAP - MIN_PREVIEW_WIDTH;
1122
+ const ratioCeiling = Math.floor(paneWidth * MAX_LEFT_RATIO);
1123
+ const ceiling = Math.min(previewSafetyCeiling, ratioCeiling);
1124
+ return Math.min(Math.max(labelDriven, slackDonation), Math.max(1, ceiling));
1125
+ }
1126
+ __name(crossTabLeftWidthWithDonation, "crossTabLeftWidthWithDonation");
1127
+ function columnWidths(paneWidth, adaptiveLeft) {
1128
+ const gap = PREVIEW_COLUMN_GAP;
1129
+ const leftWidth = Math.min(adaptiveLeft, Math.max(1, paneWidth - gap - 1));
1130
+ const rightWidth = Math.max(1, paneWidth - leftWidth - gap);
1131
+ return { leftWidth, rightWidth, gap };
1132
+ }
1133
+ __name(columnWidths, "columnWidths");
1134
+ function bodyWidths(paneWidth, mode, adaptiveLeft) {
1135
+ if (mode === "stacked") return { optionsWidth: paneWidth, previewWidth: paneWidth };
1136
+ const { leftWidth, rightWidth } = columnWidths(paneWidth, adaptiveLeft);
1137
+ return { optionsWidth: leftWidth, previewWidth: Math.max(1, rightWidth - PREVIEW_PADDING_LEFT) };
1138
+ }
1139
+ __name(bodyWidths, "bodyWidths");
1140
+
1141
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-pane.ts?ext=rpiv-ask-user-question
1142
+ var MIN_BOX_ROWS = 3;
1143
+ var PreviewPane = class {
1144
+ static {
1145
+ __name(this, "PreviewPane");
1146
+ }
1147
+ question;
1148
+ getTerminalWidth;
1149
+ optionListView;
1150
+ previewBlock;
1151
+ theme;
1152
+ props;
1153
+ /**
1154
+ * Rows the dialog can give this body this frame, set by `DialogView` before each render;
1155
+ * undefined when unconstrained. A body taller than this fits itself instead of being cut
1156
+ * by the dialog's scroll window: the options scroll within their column around the focused
1157
+ * option, and the preview box shrinks but always closes (#210).
1158
+ */
1159
+ rowAllowance;
1160
+ /**
1161
+ * Cross-tab max left-width getter. Set exactly once by `buildQuestionnaire.injectGlobalLeftWidth`
1162
+ * before any render. Initialized to a throwing sentinel so missing injection is a hard fail
1163
+ * rather than a silent fallback to a magic constant — render is illegal until injected.
1164
+ */
1165
+ globalLeftWidth = /* @__PURE__ */ __name(() => {
1166
+ throw new Error("PreviewPane.setGlobalLeftWidth must be called before render()");
1167
+ }, "globalLeftWidth");
1168
+ constructor(config) {
1169
+ this.question = config.question;
1170
+ this.getTerminalWidth = config.getTerminalWidth;
1171
+ this.optionListView = config.optionListView;
1172
+ this.previewBlock = config.previewBlock;
1173
+ this.theme = config.theme;
1174
+ this.props = { notesVisible: false, hasNote: false, selectedIndex: 0, focused: false, inputMode: false };
1175
+ }
1176
+ setGlobalLeftWidth(getter) {
1177
+ this.globalLeftWidth = getter;
1178
+ }
1179
+ getAdaptiveLeft(paneWidth) {
1180
+ return this.globalLeftWidth(paneWidth);
1181
+ }
1182
+ setProps(props) {
1183
+ this.props = props;
1184
+ }
1185
+ /** Render-time geometry from `DialogView` (not canonical state, so not a prop). */
1186
+ /** The notes affordance under the preview: shown while focused with the notes editor closed. */
1187
+ notesAffordance() {
1188
+ const { focused, notesVisible, hasNote } = this.props;
1189
+ if (!focused || notesVisible) return "none";
1190
+ return hasNote ? "edit" : "add";
1191
+ }
1192
+ setRowAllowance(rows) {
1193
+ this.rowAllowance = rows;
1194
+ }
1195
+ handleInput(_data) {
1196
+ }
1197
+ invalidate() {
1198
+ this.previewBlock.invalidate();
1199
+ this.optionListView.invalidate();
1200
+ }
1201
+ render(width) {
1202
+ if (this.question.multiSelect === true) return this.optionListView.render(width);
1203
+ if (!this.previewBlock.hasAnyPreview()) return this.optionListView.render(width);
1204
+ if (this.props.inputMode) return this.optionListView.render(width);
1205
+ return this.layout(width).lines;
1206
+ }
1207
+ /**
1208
+ * Options + preview for the current layout mode, fitted to `rowAllowance` when set. The
1209
+ * returned focus range is in the same row coordinates as `lines`, so `render`,
1210
+ * `focusedItemRowRange` and `naturalHeight` agree by construction.
1211
+ */
1212
+ layout(width) {
1213
+ const mode = decideLayout(this.getTerminalWidth(), width);
1214
+ return mode === "side-by-side" ? this.layoutSideBySide(width, mode) : this.layoutStacked(width, mode);
1215
+ }
1216
+ /**
1217
+ * Stacked: options, a blank gap, then the preview block. When that exceeds the allowance,
1218
+ * keep the focused option (plus a row either side for the overflow arrows) and give the
1219
+ * preview the rest, so the preview of the option being considered stays in view instead
1220
+ * of falling below the dialog's scroll window.
1221
+ */
1222
+ layoutStacked(width, mode) {
1223
+ const { selectedIndex } = this.props;
1224
+ const affordance = this.notesAffordance();
1225
+ const options = this.optionListView.render(width);
1226
+ const focus = this.optionListView.focusedItemRowRange(width);
1227
+ const gap = Array(STACKED_GAP_ROWS).fill("");
1228
+ const allowance = this.rowAllowance;
1229
+ const fullBlock = this.previewBlock.blockHeight(width, selectedIndex, mode);
1230
+ if (allowance === void 0 || options.length + STACKED_GAP_ROWS + fullBlock <= allowance) {
1231
+ return {
1232
+ lines: [...options, ...gap, ...this.previewBlock.renderBlock(width, selectedIndex, mode, affordance)],
1233
+ focus
1234
+ };
1235
+ }
1236
+ const focusRows = focus ? focus[1] - focus[0] : 1;
1237
+ const previewRowsWith = /* @__PURE__ */ __name((optionsFloor) => Math.min(fullBlock, allowance - STACKED_GAP_ROWS - Math.min(options.length, optionsFloor)), "previewRowsWith");
1238
+ const roomy = previewRowsWith(focusRows + 2);
1239
+ const previewRows = roomy >= MIN_BOX_ROWS ? roomy : previewRowsWith(focusRows);
1240
+ const block = this.previewBlock.renderBlock(width, selectedIndex, mode, affordance, previewRows);
1241
+ const optionRows = allowance - (block.length > 0 ? STACKED_GAP_ROWS + block.length : 0);
1242
+ const windowed = windowLines(options, Math.max(1, optionRows), focus, this.theme);
1243
+ return {
1244
+ lines: block.length > 0 ? [...windowed.lines, ...gap, ...block] : windowed.lines,
1245
+ focus: windowed.focus
1246
+ };
1247
+ }
1248
+ /**
1249
+ * Side-by-side: options column left, preview block right. Under an allowance the options
1250
+ * column scrolls around the focused option and the preview block shrinks, so neither
1251
+ * column exceeds it and the dialog never slices through the preview box.
1252
+ */
1253
+ layoutSideBySide(width, mode) {
1254
+ const adaptiveLeft = this.getAdaptiveLeft(width);
1255
+ const { leftWidth, rightWidth, gap } = columnWidths(width, adaptiveLeft);
1256
+ const allowance = this.rowAllowance;
1257
+ const left = allowance === void 0 ? {
1258
+ lines: this.optionListView.render(leftWidth),
1259
+ focus: this.optionListView.focusedItemRowRange(leftWidth)
1260
+ } : windowLines(
1261
+ this.optionListView.render(leftWidth),
1262
+ allowance,
1263
+ this.optionListView.focusedItemRowRange(leftWidth),
1264
+ this.theme
1265
+ );
1266
+ const rightLines = this.renderPaddedPreviewLines(rightWidth, mode, allowance);
1267
+ const rows = Math.max(left.lines.length, rightLines.length);
1268
+ const gapStr = " ".repeat(gap);
1269
+ const out = [];
1270
+ for (let i = 0; i < rows; i++) {
1271
+ const leftRaw = left.lines[i] ?? "";
1272
+ const rightRaw = rightLines[i] ?? "";
1273
+ const leftClamped = truncateToWidth(leftRaw, leftWidth, "");
1274
+ const leftPad = " ".repeat(Math.max(0, leftWidth - visibleWidth(leftClamped)));
1275
+ const joined = `${leftClamped}${leftPad}${gapStr}${rightRaw}`;
1276
+ out.push(truncateToWidth(joined, width, ""));
1277
+ }
1278
+ return { lines: out, focus: left.focus };
1279
+ }
1280
+ focusedItemRowRange(width) {
1281
+ if (this.question.multiSelect === true) return this.optionListView.focusedItemRowRange(width);
1282
+ if (!this.previewBlock.hasAnyPreview()) return this.optionListView.focusedItemRowRange(width);
1283
+ if (this.props.inputMode) return this.optionListView.focusedItemRowRange(width);
1284
+ return this.layout(width).focus ?? [0, 1];
1285
+ }
1286
+ naturalHeight(width) {
1287
+ if (this.question.multiSelect === true) return this.optionListView.render(width).length;
1288
+ if (!this.previewBlock.hasAnyPreview()) return this.optionListView.render(width).length;
1289
+ if (this.props.inputMode) return this.optionListView.render(width).length;
1290
+ if (this.rowAllowance !== void 0) return this.layout(width).lines.length;
1291
+ const mode = decideLayout(this.getTerminalWidth(), width);
1292
+ const adaptiveLeft = this.getAdaptiveLeft(width);
1293
+ const { optionsWidth, previewWidth } = bodyWidths(width, mode, adaptiveLeft);
1294
+ const optionsHeight = this.optionListView.render(optionsWidth).length;
1295
+ const previewBlockHeight = this.previewBlock.blockHeight(previewWidth, this.props.selectedIndex, mode);
1296
+ if (mode === "side-by-side") return Math.max(optionsHeight, previewBlockHeight);
1297
+ return optionsHeight + STACKED_GAP_ROWS + previewBlockHeight;
1298
+ }
1299
+ maxNaturalHeight(width) {
1300
+ if (this.question.multiSelect === true) return this.optionListView.render(width).length;
1301
+ if (!this.previewBlock.hasAnyPreview()) return this.optionListView.render(width).length;
1302
+ if (this.props.inputMode) return this.optionListView.render(width).length;
1303
+ const mode = decideLayout(this.getTerminalWidth(), width);
1304
+ const adaptiveLeft = this.getAdaptiveLeft(width);
1305
+ const { optionsWidth, previewWidth } = bodyWidths(width, mode, adaptiveLeft);
1306
+ const optionsHeight = this.optionListView.render(optionsWidth).length;
1307
+ let maxPreviewBlock = 0;
1308
+ for (let i = 0; i < this.question.options.length; i++) {
1309
+ const h = this.previewBlock.blockHeight(previewWidth, i, mode);
1310
+ if (h > maxPreviewBlock) maxPreviewBlock = h;
1311
+ }
1312
+ const max = mode === "side-by-side" ? Math.max(optionsHeight, maxPreviewBlock) : optionsHeight + STACKED_GAP_ROWS + maxPreviewBlock;
1313
+ return this.rowAllowance === void 0 ? max : Math.min(max, this.rowAllowance);
1314
+ }
1315
+ renderPaddedPreviewLines(colWidth, mode, maxRows) {
1316
+ const inner = Math.max(1, colWidth - PREVIEW_PADDING_LEFT);
1317
+ const contentLines = this.previewBlock.renderBlock(
1318
+ inner,
1319
+ this.props.selectedIndex,
1320
+ mode,
1321
+ this.notesAffordance(),
1322
+ maxRows
1323
+ );
1324
+ const boxWidth = Math.max(1, visibleWidth(contentLines[0] ?? ""));
1325
+ const boxAlignedPad = Math.max(PREVIEW_PADDING_LEFT, colWidth - boxWidth);
1326
+ return contentLines.map((line) => {
1327
+ if (line === "") return "";
1328
+ const pad = Math.max(PREVIEW_PADDING_LEFT, Math.min(boxAlignedPad, colWidth - visibleWidth(line)));
1329
+ return `${" ".repeat(pad)}${truncateToWidth(line, colWidth - pad, "")}`;
1330
+ });
1331
+ }
1332
+ };
1333
+
1334
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/submit-picker.ts?ext=rpiv-ask-user-question
1335
+ var ACTIVE_POINTER2 = "\u276F ";
1336
+ var INACTIVE_POINTER2 = " ";
1337
+ var NUMBER_SEPARATOR2 = ". ";
1338
+ var SUBMIT_LABEL = "Submit answers";
1339
+ var CANCEL_LABEL = "Cancel";
1340
+ var SubmitPicker = class {
1341
+ constructor(theme) {
1342
+ this.theme = theme;
1343
+ this.props = { rows: [{ active: false }, { active: false }] };
1344
+ }
1345
+ theme;
1346
+ static {
1347
+ __name(this, "SubmitPicker");
1348
+ }
1349
+ props;
1350
+ setProps(props) {
1351
+ this.props = props;
1352
+ }
1353
+ handleInput(_data) {
1354
+ }
1355
+ invalidate() {
1356
+ }
1357
+ naturalHeight(_width) {
1358
+ return 2;
1359
+ }
1360
+ render(width) {
1361
+ const lines = [];
1362
+ for (let i = 0; i < 2; i++) {
1363
+ const text = i === 0 ? t("submit.label", SUBMIT_LABEL) : t("submit.cancel", CANCEL_LABEL);
1364
+ const active = this.props.rows[i]?.active ?? false;
1365
+ const pointer = active ? ACTIVE_POINTER2 : INACTIVE_POINTER2;
1366
+ const number = `${i + 1}${NUMBER_SEPARATOR2}`;
1367
+ const label = active ? this.theme.fg("accent", this.theme.bold(text)) : this.theme.fg("text", text);
1368
+ lines.push(truncateToWidth(`${pointer}${number}${label}`, width, ""));
1369
+ }
1370
+ return lines;
1371
+ }
1372
+ };
1373
+
1374
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/components/tab-bar.ts?ext=rpiv-ask-user-question
1375
+ var TabBar = class {
1376
+ constructor(theme) {
1377
+ this.theme = theme;
1378
+ this.props = { tabs: [], submit: { active: false, allAnswered: false } };
1379
+ }
1380
+ theme;
1381
+ static {
1382
+ __name(this, "TabBar");
1383
+ }
1384
+ props;
1385
+ setProps(props) {
1386
+ this.props = props;
1387
+ }
1388
+ handleInput(_data) {
1389
+ }
1390
+ invalidate() {
1391
+ }
1392
+ render(width) {
1393
+ const pieces = [" \u2190 "];
1394
+ for (const tab of this.props.tabs) {
1395
+ const box = tab.answered ? "\u25A0" : "\u25A1";
1396
+ const rawSeg = ` ${box} ${tab.label} `;
1397
+ const styled = tab.active ? this.theme.bg("selectedBg", this.theme.fg("text", rawSeg)) : this.theme.fg(tab.answered ? "success" : "muted", rawSeg);
1398
+ pieces.push(styled);
1399
+ pieces.push(" ");
1400
+ }
1401
+ const submitText = " \u2713 Submit ";
1402
+ const submitStyled = this.props.submit.active ? this.theme.bg("selectedBg", this.theme.fg("text", submitText)) : this.theme.fg(this.props.submit.allAnswered ? "success" : "dim", submitText);
1403
+ pieces.push(submitStyled);
1404
+ pieces.push(" \u2192");
1405
+ const tabLine = truncateToWidth(pieces.join(""), width, "");
1406
+ return [tabLine, ""];
1407
+ }
1408
+ };
1409
+
1410
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/focus.ts?ext=rpiv-ask-user-question
1411
+ function selectActiveView(state, totalQuestions) {
1412
+ if (state.notesVisible) return "notes";
1413
+ if (state.currentTab === totalQuestions) return "submit";
1414
+ return "options";
1415
+ }
1416
+ __name(selectActiveView, "selectActiveView");
1417
+
1418
+ // node_modules/@juicesharp/rpiv-ask-user-question/view/props-adapter.ts?ext=rpiv-ask-user-question
1419
+ function getInputCursorOffset(input) {
1420
+ const lines = input.getLines();
1421
+ const cursor = input.getCursor();
1422
+ let offset = cursor.col;
1423
+ for (let i = 0; i < cursor.line; i++) offset += (lines[i]?.length ?? 0) + 1;
1424
+ return offset;
1425
+ }
1426
+ __name(getInputCursorOffset, "getInputCursorOffset");
1427
+ var QuestionnairePropsAdapter = class {
1428
+ static {
1429
+ __name(this, "QuestionnairePropsAdapter");
1430
+ }
1431
+ tui;
1432
+ questions;
1433
+ itemsByTab;
1434
+ tabsByIndex;
1435
+ inlineInput;
1436
+ globalBindings;
1437
+ perTabBindings;
1438
+ extraInvalidatables;
1439
+ constructor(config) {
1440
+ this.tui = config.tui;
1441
+ this.questions = config.questions;
1442
+ this.itemsByTab = config.itemsByTab;
1443
+ this.tabsByIndex = config.tabsByIndex;
1444
+ this.inlineInput = config.inlineInput;
1445
+ this.globalBindings = config.globalBindings;
1446
+ this.perTabBindings = config.perTabBindings;
1447
+ this.extraInvalidatables = config.extraInvalidatables ?? [];
1448
+ }
1449
+ apply(state) {
1450
+ const totalQuestions = this.questions.length;
1451
+ const activeView = selectActiveView(state, totalQuestions);
1452
+ const paneIndex = selectActivePreviewPaneIndex(state.currentTab, totalQuestions);
1453
+ const activePreviewPane = this.tabsByIndex[paneIndex]?.preview ?? this.tabsByIndex[0].preview;
1454
+ const ctx = {
1455
+ questions: this.questions,
1456
+ itemsByTab: this.itemsByTab,
1457
+ totalQuestions,
1458
+ activeView,
1459
+ inputBuffer: this.inlineInput.getText(),
1460
+ inputCursorOffset: getInputCursorOffset(this.inlineInput),
1461
+ activePreviewPane
1462
+ };
1463
+ for (const binding of this.globalBindings) {
1464
+ binding.apply(state, ctx);
1465
+ }
1466
+ for (let i = 0; i < this.tabsByIndex.length; i++) {
1467
+ const tab = this.tabsByIndex[i];
1468
+ const tabCtx = { ...ctx, tab, i };
1469
+ for (const binding of this.perTabBindings) {
1470
+ binding.apply(state, tabCtx);
1471
+ }
1472
+ }
1473
+ this.tui.requestRender();
1474
+ }
1475
+ /**
1476
+ * Invalidates every owned renderable. Called by the session in place of
1477
+ * the old `dialog.invalidate()` forwarding chain — DialogView no longer
1478
+ * reaches into siblings (tabBar, notesInput, activePreviewPane).
1479
+ * Iterates the same registries used by `apply()` plus
1480
+ * `extraInvalidatables` for components outside the binding system.
1481
+ */
1482
+ invalidate() {
1483
+ for (const b of this.globalBindings) b.invalidate();
1484
+ for (const tab of this.tabsByIndex) {
1485
+ tab.optionList.invalidate();
1486
+ tab.preview.invalidate();
1487
+ tab.multiSelect?.invalidate();
1488
+ }
1489
+ for (const x of this.extraInvalidatables) x.invalidate();
1490
+ }
1491
+ };
1492
+
1493
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts?ext=rpiv-ask-user-question
1494
+ function emptyMultiSelectProps(ctx) {
1495
+ return {
1496
+ rows: [],
1497
+ other: {
1498
+ active: false,
1499
+ checked: false,
1500
+ inputMode: false,
1501
+ inputBuffer: ctx.inputBuffer,
1502
+ inputCursorOffset: ctx.inputCursorOffset
1503
+ },
1504
+ nextActive: false,
1505
+ nextLabel: displayLabel("next")
1506
+ };
1507
+ }
1508
+ __name(emptyMultiSelectProps, "emptyMultiSelectProps");
1509
+ function nextLabelFor(ctx) {
1510
+ const isLastQuestion = ctx.i === ctx.questions.length - 1;
1511
+ return isLastQuestion ? MULTI_SUBMIT_LABEL : displayLabel("next");
1512
+ }
1513
+ __name(nextLabelFor, "nextLabelFor");
1514
+ var selectMultiSelectProps = /* @__PURE__ */ __name((state, ctx) => {
1515
+ const question = ctx.questions[ctx.i];
1516
+ if (!question) {
1517
+ return emptyMultiSelectProps(ctx);
1518
+ }
1519
+ const focused = ctx.activeView === "options";
1520
+ const rows = [];
1521
+ for (let i = 0; i < question.options.length; i++) {
1522
+ rows.push({
1523
+ checked: state.multiSelectChecked.has(i),
1524
+ active: focused && i === state.optionIndex
1525
+ });
1526
+ }
1527
+ const otherActive = focused && state.optionIndex === question.options.length;
1528
+ const nextActive = focused && state.optionIndex === question.options.length + 1;
1529
+ return {
1530
+ rows,
1531
+ other: {
1532
+ active: otherActive,
1533
+ checked: ctx.inputBuffer.trim().length > 0,
1534
+ inputMode: state.inputMode,
1535
+ inputBuffer: ctx.inputBuffer,
1536
+ inputCursorOffset: ctx.inputCursorOffset
1537
+ },
1538
+ nextActive,
1539
+ nextLabel: nextLabelFor(ctx)
1540
+ };
1541
+ }, "selectMultiSelectProps");
1542
+ var selectOptionListProps = /* @__PURE__ */ __name((state, ctx) => {
1543
+ const items = ctx.itemsByTab[ctx.i] ?? [];
1544
+ const focused = ctx.activeView === "options";
1545
+ const confirmed = selectConfirmedIndicator(ctx.questions, state.currentTab, state.answers, items);
1546
+ return {
1547
+ selectedIndex: state.optionIndex,
1548
+ focused,
1549
+ inputBuffer: ctx.inputBuffer,
1550
+ inputCursorOffset: ctx.inputCursorOffset,
1551
+ ...confirmed ? { confirmed } : {}
1552
+ };
1553
+ }, "selectOptionListProps");
1554
+ var selectSubmitPickerProps = /* @__PURE__ */ __name((state, ctx) => {
1555
+ const focused = ctx.activeView === "submit";
1556
+ return {
1557
+ rows: [
1558
+ { active: focused && state.submitChoiceIndex === 0 },
1559
+ { active: focused && state.submitChoiceIndex === 1 }
1560
+ ]
1561
+ };
1562
+ }, "selectSubmitPickerProps");
1563
+ var selectPreviewPaneProps = /* @__PURE__ */ __name((state, ctx) => ({
1564
+ notesVisible: state.notesVisible,
1565
+ hasNote: selectSavedNote(state.notesByTab, state.answers, ctx.i) !== void 0,
1566
+ selectedIndex: state.optionIndex,
1567
+ focused: ctx.activeView === "options",
1568
+ inputMode: state.inputMode
1569
+ }), "selectPreviewPaneProps");
1570
+ var selectTabBarProps = /* @__PURE__ */ __name((state, ctx) => {
1571
+ const tabs = ctx.questions.map((q, i) => ({
1572
+ label: q.header && q.header.length > 0 ? q.header : `Q${i + 1}`,
1573
+ answered: state.answers.has(i),
1574
+ active: i === state.currentTab
1575
+ }));
1576
+ return {
1577
+ tabs,
1578
+ submit: {
1579
+ active: state.currentTab === ctx.questions.length,
1580
+ allAnswered: state.answers.size === ctx.questions.length && ctx.questions.length > 0
1581
+ }
1582
+ };
1583
+ }, "selectTabBarProps");
1584
+ var selectDialogProps = /* @__PURE__ */ __name((state, ctx) => ({
1585
+ state,
1586
+ activePreviewPane: ctx.activePreviewPane
1587
+ }), "selectDialogProps");
1588
+
1589
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts?ext=rpiv-ask-user-question
1590
+ function previewBodyHeights(pane) {
1591
+ return (width) => {
1592
+ const current = pane.naturalHeight(width);
1593
+ return { current, max: Math.max(current, pane.maxNaturalHeight(width)) };
1594
+ };
1595
+ }
1596
+ __name(previewBodyHeights, "previewBodyHeights");
1597
+ function editorTheme(theme) {
1598
+ return {
1599
+ borderColor: /* @__PURE__ */ __name((text) => theme.fg("borderMuted", text), "borderColor"),
1600
+ selectList: {
1601
+ selectedPrefix: /* @__PURE__ */ __name((text) => theme.bg("selectedBg", theme.fg("accent", text)), "selectedPrefix"),
1602
+ selectedText: /* @__PURE__ */ __name((text) => theme.bg("selectedBg", theme.bold(text)), "selectedText"),
1603
+ description: /* @__PURE__ */ __name((text) => theme.fg("muted", text), "description"),
1604
+ scrollInfo: /* @__PURE__ */ __name((text) => theme.fg("dim", text), "scrollInfo"),
1605
+ noMatch: /* @__PURE__ */ __name((text) => theme.fg("warning", text), "noMatch")
1606
+ }
1607
+ };
1608
+ }
1609
+ __name(editorTheme, "editorTheme");
1610
+ function multiSelectBodyHeights(view) {
1611
+ return (width) => {
1612
+ const h = view.naturalHeight(width);
1613
+ return { current: h, max: h };
1614
+ };
1615
+ }
1616
+ __name(multiSelectBodyHeights, "multiSelectBodyHeights");
1617
+ var isActiveTab = /* @__PURE__ */ __name((s, ctx) => ctx.i === selectActivePreviewPaneIndex(s.currentTab, ctx.totalQuestions), "isActiveTab");
1618
+ function buildQuestionnaire(config) {
1619
+ return new QuestionnaireBuilder(config).build();
1620
+ }
1621
+ __name(buildQuestionnaire, "buildQuestionnaire");
1622
+ var QuestionnaireBuilder = class {
1623
+ static {
1624
+ __name(this, "QuestionnaireBuilder");
1625
+ }
1626
+ tui;
1627
+ theme;
1628
+ questions;
1629
+ itemsByTab;
1630
+ isMulti;
1631
+ initialState;
1632
+ getCurrentTab;
1633
+ collapseKey;
1634
+ rowBudget;
1635
+ selectTheme;
1636
+ markdownTheme = getMarkdownTheme();
1637
+ notesInput;
1638
+ inlineInput;
1639
+ getTerminalWidth = /* @__PURE__ */ __name(() => this.tui.terminal.columns, "getTerminalWidth");
1640
+ getTerminalRows = /* @__PURE__ */ __name(() => this.rowBudget(this.tui.terminal.rows), "getTerminalRows");
1641
+ constructor(config) {
1642
+ this.tui = config.tui;
1643
+ this.theme = config.theme;
1644
+ this.questions = config.questions;
1645
+ this.itemsByTab = config.itemsByTab;
1646
+ this.isMulti = config.isMulti;
1647
+ this.initialState = config.initialState;
1648
+ this.getCurrentTab = config.getCurrentTab;
1649
+ this.collapseKey = config.collapseKey;
1650
+ this.rowBudget = config.rowBudget;
1651
+ this.selectTheme = this.makeSelectTheme();
1652
+ const textEditorTheme = editorTheme(this.theme);
1653
+ this.notesInput = new Editor(this.tui, textEditorTheme);
1654
+ this.inlineInput = new Editor(this.tui, textEditorTheme);
1655
+ this.notesInput.disableSubmit = true;
1656
+ this.inlineInput.disableSubmit = true;
1657
+ }
1658
+ build() {
1659
+ const tabs = this.buildTabComponents();
1660
+ this.injectGlobalLeftWidth(tabs);
1661
+ const submitPicker = this.buildSubmitPicker();
1662
+ const tabBar = this.buildTabBar();
1663
+ const heights = this.buildHeightComputers(tabs);
1664
+ const dialog = this.buildDialog(tabs, submitPicker, tabBar, heights);
1665
+ const globalBindings = this.buildGlobalBindings(dialog, submitPicker, tabBar);
1666
+ const perTabBindings = this.buildPerTabBindings();
1667
+ const adapter = this.buildAdapter(tabs, globalBindings, perTabBindings);
1668
+ return this.handle(adapter, dialog);
1669
+ }
1670
+ makeSelectTheme() {
1671
+ const t2 = this.theme;
1672
+ return {
1673
+ selectedText: /* @__PURE__ */ __name((s) => t2.fg("accent", t2.bold(s)), "selectedText"),
1674
+ description: /* @__PURE__ */ __name((s) => t2.fg("muted", s), "description"),
1675
+ scrollInfo: /* @__PURE__ */ __name((s) => t2.fg("dim", s), "scrollInfo")
1676
+ };
1677
+ }
1678
+ buildTabComponents() {
1679
+ return this.questions.map((q, i) => this.buildTabFor(q, i));
1680
+ }
1681
+ buildTabFor(question, index) {
1682
+ const optionList = new OptionListView({
1683
+ items: this.itemsByTab[index] ?? [],
1684
+ theme: this.selectTheme
1685
+ });
1686
+ const previewBlock = new PreviewBlockRenderer({
1687
+ question,
1688
+ theme: this.theme,
1689
+ markdownTheme: this.markdownTheme
1690
+ });
1691
+ const preview = new PreviewPane({
1692
+ question,
1693
+ theme: this.theme,
1694
+ getTerminalWidth: this.getTerminalWidth,
1695
+ optionListView: optionList,
1696
+ previewBlock
1697
+ });
1698
+ const multiSelect = question.multiSelect ? new MultiSelectView(this.theme, question) : void 0;
1699
+ const bodyHeights = this.buildBodyHeights(question, preview, multiSelect);
1700
+ return { optionList, preview, multiSelect, bodyHeights };
1701
+ }
1702
+ buildBodyHeights(question, preview, multiSelect) {
1703
+ return question.multiSelect ? multiSelectBodyHeights(multiSelect) : previewBodyHeights(preview);
1704
+ }
1705
+ /**
1706
+ * Compute cross-tab max adaptive left width and inject into each PreviewPane.
1707
+ * Mirrors buildHeightComputers pattern — iterates all tabs, takes max.
1708
+ * Called after buildTabComponents, before buildDialog (so setGlobalLeftWidth
1709
+ * is set before any rendering occurs).
1710
+ */
1711
+ injectGlobalLeftWidth(tabs) {
1712
+ const questions = this.questions;
1713
+ const itemsByTab = this.itemsByTab;
1714
+ const tabsDescriptor = questions.map((q) => ({ multiSelect: q.multiSelect }));
1715
+ const globalLeftWidth = /* @__PURE__ */ __name((paneWidth) => crossTabLeftWidthWithDonation(tabsDescriptor, itemsByTab, questions, paneWidth), "globalLeftWidth");
1716
+ for (const tab of tabs) {
1717
+ tab.preview.setGlobalLeftWidth(globalLeftWidth);
1718
+ }
1719
+ }
1720
+ buildSubmitPicker() {
1721
+ return this.isMulti ? new SubmitPicker(this.theme) : void 0;
1722
+ }
1723
+ buildTabBar() {
1724
+ return this.isMulti ? new TabBar(this.theme) : void 0;
1725
+ }
1726
+ buildHeightComputers(tabs) {
1727
+ const global = /* @__PURE__ */ __name((width) => {
1728
+ let max = 0;
1729
+ for (const tab of tabs) {
1730
+ const h = tab.bodyHeights(width).max;
1731
+ if (h > max) max = h;
1732
+ }
1733
+ return Math.max(1, max);
1734
+ }, "global");
1735
+ const current = /* @__PURE__ */ __name((width) => {
1736
+ const idx = Math.min(this.getCurrentTab(), tabs.length - 1);
1737
+ return Math.max(0, tabs[idx]?.bodyHeights(width).current ?? 0);
1738
+ }, "current");
1739
+ return { global, current };
1740
+ }
1741
+ pickInitialActivePreview(tabs) {
1742
+ const idx = selectActivePreviewPaneIndex(this.initialState.currentTab, this.questions.length);
1743
+ return tabs[idx]?.preview ?? tabs[0].preview;
1744
+ }
1745
+ buildDialog(tabs, submitPicker, tabBar, heights) {
1746
+ return new DialogView(
1747
+ {
1748
+ theme: this.theme,
1749
+ questions: this.questions,
1750
+ tabBar,
1751
+ notesInput: this.notesInput,
1752
+ isMulti: this.isMulti,
1753
+ tabsByIndex: tabs,
1754
+ submitPicker,
1755
+ getBodyHeight: heights.global,
1756
+ getCurrentBodyHeight: heights.current,
1757
+ getTerminalRows: this.getTerminalRows,
1758
+ collapseKey: this.collapseKey
1759
+ },
1760
+ { state: this.initialState, activePreviewPane: this.pickInitialActivePreview(tabs) }
1761
+ );
1762
+ }
1763
+ buildGlobalBindings(dialog, submitPicker, tabBar) {
1764
+ return [
1765
+ globalBinding({ component: dialog, select: selectDialogProps }),
1766
+ ...submitPicker ? [globalBinding({ component: submitPicker, select: selectSubmitPickerProps })] : [],
1767
+ ...tabBar ? [globalBinding({ component: tabBar, select: selectTabBarProps })] : []
1768
+ ];
1769
+ }
1770
+ buildPerTabBindings() {
1771
+ return [
1772
+ perTabBinding({
1773
+ resolve: /* @__PURE__ */ __name((tab) => tab.optionList, "resolve"),
1774
+ predicate: isActiveTab,
1775
+ select: selectOptionListProps
1776
+ }),
1777
+ perTabBinding({
1778
+ resolve: /* @__PURE__ */ __name((tab) => tab.preview, "resolve"),
1779
+ predicate: isActiveTab,
1780
+ select: selectPreviewPaneProps
1781
+ }),
1782
+ perTabBinding({
1783
+ resolve: /* @__PURE__ */ __name((tab) => tab.multiSelect, "resolve"),
1784
+ select: selectMultiSelectProps
1785
+ })
1786
+ ];
1787
+ }
1788
+ buildAdapter(tabs, globalBindings, perTabBindings) {
1789
+ return new QuestionnairePropsAdapter({
1790
+ tui: this.tui,
1791
+ questions: this.questions,
1792
+ itemsByTab: this.itemsByTab,
1793
+ tabsByIndex: tabs,
1794
+ inlineInput: this.inlineInput,
1795
+ globalBindings,
1796
+ perTabBindings,
1797
+ extraInvalidatables: [this.notesInput]
1798
+ });
1799
+ }
1800
+ handle(adapter, dialog) {
1801
+ return {
1802
+ adapter,
1803
+ notesInput: this.notesInput,
1804
+ inlineInput: this.inlineInput,
1805
+ render: /* @__PURE__ */ __name((w) => dialog.render(w), "render"),
1806
+ invalidate: /* @__PURE__ */ __name(() => adapter.invalidate(), "invalidate")
1807
+ };
1808
+ }
1809
+ };
1810
+
1811
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts?ext=rpiv-ask-user-question
1812
+ var KEYBIND_UP = "tui.select.up";
1813
+ var KEYBIND_DOWN = "tui.select.down";
1814
+ var KEYBIND_CONFIRM = "tui.select.confirm";
1815
+ var KEYBIND_SUBMIT = "tui.input.submit";
1816
+ var KEYBIND_CANCEL = "tui.select.cancel";
1817
+ var KEYBIND_NEW_LINE = "tui.input.newLine";
1818
+ var KEYBIND_EDITOR_UP = "tui.editor.cursorUp";
1819
+ var KEYBIND_EDITOR_DOWN = "tui.editor.cursorDown";
1820
+ var KEYBIND_CLEAR = "tui.editor.deleteToLineStart";
1821
+ var KEYBIND_EXTERNAL_EDITOR = "app.editor.external";
1822
+ var NOTES_ACTIVATE_KEY = "n";
1823
+ var SPACE_KEY = " ";
1824
+ function isConfirm(kb, data) {
1825
+ return kb.matches(data, KEYBIND_CONFIRM) || kb.matches(data, KEYBIND_SUBMIT);
1826
+ }
1827
+ __name(isConfirm, "isConfirm");
1828
+ function wrapTab(index, total) {
1829
+ if (total <= 0) return 0;
1830
+ return (index % total + total) % total;
1831
+ }
1832
+ __name(wrapTab, "wrapTab");
1833
+ function totalTabs(runtime) {
1834
+ return runtime.isMulti ? runtime.questions.length + 1 : 1;
1835
+ }
1836
+ __name(totalTabs, "totalTabs");
1837
+ function computeAutoAdvanceTab(state, runtime) {
1838
+ if (!runtime.isMulti) return void 0;
1839
+ if (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;
1840
+ return runtime.questions.length;
1841
+ }
1842
+ __name(computeAutoAdvanceTab, "computeAutoAdvanceTab");
1843
+ function buildSingleSelectAnswer(state, runtime) {
1844
+ const q = runtime.questions[state.currentTab];
1845
+ if (!q) return null;
1846
+ const item = runtime.currentItem;
1847
+ if (state.inputMode) {
1848
+ const label = runtime.inputBuffer;
1849
+ return {
1850
+ questionIndex: state.currentTab,
1851
+ question: q.question,
1852
+ kind: "custom",
1853
+ answer: label.length > 0 ? label : null
1854
+ };
1855
+ }
1856
+ if (!item) return null;
1857
+ if (item.kind === "other") {
1858
+ return null;
1859
+ }
1860
+ if (item.kind === "next") {
1861
+ return null;
1862
+ }
1863
+ return {
1864
+ questionIndex: state.currentTab,
1865
+ question: q.question,
1866
+ kind: "option",
1867
+ answer: item.label
1868
+ };
1869
+ }
1870
+ __name(buildSingleSelectAnswer, "buildSingleSelectAnswer");
1871
+ function buildMultiSelected(state, runtime) {
1872
+ const q = runtime.questions[state.currentTab];
1873
+ if (!q) return [];
1874
+ const out = [];
1875
+ for (let i = 0; i < q.options.length; i++) {
1876
+ if (state.multiSelectChecked.has(i)) {
1877
+ const label = q.options[i]?.label;
1878
+ if (typeof label === "string") out.push(label);
1879
+ }
1880
+ }
1881
+ return out;
1882
+ }
1883
+ __name(buildMultiSelected, "buildMultiSelected");
1884
+ function buildMultiConfirmAction(state, runtime) {
1885
+ return {
1886
+ kind: "multi_confirm",
1887
+ selected: buildMultiSelected(state, runtime),
1888
+ answer: runtime.inputBuffer.trim().length > 0 ? runtime.inputBuffer : null,
1889
+ autoAdvanceTab: computeAutoAdvanceTab(state, runtime)
1890
+ };
1891
+ }
1892
+ __name(buildMultiConfirmAction, "buildMultiConfirmAction");
1893
+ function tabSwitchAction(data, state, runtime) {
1894
+ if (!runtime.isMulti) return null;
1895
+ const total = totalTabs(runtime);
1896
+ if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {
1897
+ return { kind: "tab_switch", nextTab: wrapTab(state.currentTab + 1, total) };
1898
+ }
1899
+ if (matchesKey(data, Key.shift("tab")) || matchesKey(data, Key.left)) {
1900
+ return { kind: "tab_switch", nextTab: wrapTab(state.currentTab - 1, total) };
1901
+ }
1902
+ return null;
1903
+ }
1904
+ __name(tabSwitchAction, "tabSwitchAction");
1905
+ function nextNavOnDown(state, runtime) {
1906
+ return {
1907
+ kind: "nav",
1908
+ nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)),
1909
+ inputValue: runtime.inputBuffer
1910
+ };
1911
+ }
1912
+ __name(nextNavOnDown, "nextNavOnDown");
1913
+ function prevNavOnUp(state, runtime) {
1914
+ return {
1915
+ kind: "nav",
1916
+ nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)),
1917
+ inputValue: runtime.inputBuffer
1918
+ };
1919
+ }
1920
+ __name(prevNavOnUp, "prevNavOnUp");
1921
+ function routeCollapsed(kb, data) {
1922
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
1923
+ return { kind: "ignore" };
1924
+ }
1925
+ __name(routeCollapsed, "routeCollapsed");
1926
+ function routeNotesMode(kb, data) {
1927
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "notes_exit" };
1928
+ if (kb.matches(data, KEYBIND_NEW_LINE)) return { kind: "notes_forward", data };
1929
+ if (isConfirm(kb, data)) return { kind: "notes_exit" };
1930
+ return { kind: "notes_forward", data };
1931
+ }
1932
+ __name(routeNotesMode, "routeNotesMode");
1933
+ function routeInputMode(kb, data, state, runtime) {
1934
+ if (kb.matches(data, KEYBIND_NEW_LINE)) return { kind: "ignore" };
1935
+ if (isConfirm(kb, data)) {
1936
+ const q = runtime.questions[state.currentTab];
1937
+ if (q?.multiSelect) return buildMultiConfirmAction(state, runtime);
1938
+ const answer = buildSingleSelectAnswer(state, runtime);
1939
+ if (!answer) return { kind: "ignore" };
1940
+ return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
1941
+ }
1942
+ if (kb.matches(data, KEYBIND_CLEAR)) return { kind: "input_clear" };
1943
+ if (kb.matches(data, KEYBIND_EXTERNAL_EDITOR)) return { kind: "input_edit", value: runtime.inputBuffer };
1944
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
1945
+ if (kb.matches(data, KEYBIND_EDITOR_UP) && runtime.canMoveInputUp) return { kind: "ignore" };
1946
+ if (kb.matches(data, KEYBIND_EDITOR_DOWN) && runtime.canMoveInputDown) return { kind: "ignore" };
1947
+ if (kb.matches(data, KEYBIND_UP)) return prevNavOnUp(state, runtime);
1948
+ if (kb.matches(data, KEYBIND_DOWN)) return nextNavOnDown(state, runtime);
1949
+ return { kind: "ignore" };
1950
+ }
1951
+ __name(routeInputMode, "routeInputMode");
1952
+ function routeSubmitTab(kb, data, state, runtime) {
1953
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
1954
+ const tab = tabSwitchAction(data, state, runtime);
1955
+ if (tab) return tab;
1956
+ if (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {
1957
+ const delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;
1958
+ const next = wrapTab(state.submitChoiceIndex + delta, 2);
1959
+ return { kind: "submit_nav", nextIndex: next === 1 ? 1 : 0 };
1960
+ }
1961
+ if (isConfirm(kb, data)) {
1962
+ return state.submitChoiceIndex === 1 ? { kind: "cancel" } : { kind: "submit" };
1963
+ }
1964
+ if (data === NOTES_ACTIVATE_KEY) {
1965
+ return { kind: "notes_enter" };
1966
+ }
1967
+ return { kind: "ignore" };
1968
+ }
1969
+ __name(routeSubmitTab, "routeSubmitTab");
1970
+ function routeMultiSelectTab(kb, data, state, runtime) {
1971
+ const focusedKind = runtime.currentItem?.kind;
1972
+ const focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : void 0;
1973
+ if (data === SPACE_KEY) {
1974
+ if (focusedMeta?.blocksMultiToggle) return { kind: "ignore" };
1975
+ if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
1976
+ return { kind: "toggle", index: state.optionIndex };
1977
+ }
1978
+ if (isConfirm(kb, data)) {
1979
+ if (focusedMeta?.activatesInputMode) return { kind: "ignore" };
1980
+ if (!focusedMeta?.autoSubmitsInMulti) return { kind: "toggle", index: state.optionIndex };
1981
+ return buildMultiConfirmAction(state, runtime);
1982
+ }
1983
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
1984
+ return { kind: "ignore" };
1985
+ }
1986
+ __name(routeMultiSelectTab, "routeMultiSelectTab");
1987
+ function routeSingleSelectTab(kb, data, state, runtime) {
1988
+ if (isConfirm(kb, data)) {
1989
+ const answer = buildSingleSelectAnswer(state, runtime);
1990
+ if (!answer) return { kind: "ignore" };
1991
+ return { kind: "confirm", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };
1992
+ }
1993
+ if (kb.matches(data, KEYBIND_CANCEL)) return { kind: "cancel" };
1994
+ return { kind: "ignore" };
1995
+ }
1996
+ __name(routeSingleSelectTab, "routeSingleSelectTab");
1997
+ function routeKey(data, state, runtime) {
1998
+ const kb = runtime.keybindings;
1999
+ if (typeof runtime.collapseKey === "string" && runtime.collapseKey !== "off" && matchesKey(data, runtime.collapseKey)) {
2000
+ return { kind: "toggle_collapsed" };
2001
+ }
2002
+ if (state.collapsed) return routeCollapsed(kb, data);
2003
+ if (state.notesVisible) return routeNotesMode(kb, data);
2004
+ if (state.inputMode) return routeInputMode(kb, data, state, runtime);
2005
+ if (runtime.isMulti && state.currentTab === runtime.questions.length) {
2006
+ return routeSubmitTab(kb, data, state, runtime);
2007
+ }
2008
+ const tab = tabSwitchAction(data, state, runtime);
2009
+ if (tab) return tab;
2010
+ const q = runtime.questions[state.currentTab];
2011
+ if (!q) return { kind: "ignore" };
2012
+ if (data === NOTES_ACTIVATE_KEY) {
2013
+ return { kind: "notes_enter" };
2014
+ }
2015
+ if (kb.matches(data, KEYBIND_UP)) {
2016
+ return prevNavOnUp(state, runtime);
2017
+ }
2018
+ if (kb.matches(data, KEYBIND_DOWN)) {
2019
+ return nextNavOnDown(state, runtime);
2020
+ }
2021
+ if (q.multiSelect) return routeMultiSelectTab(kb, data, state, runtime);
2022
+ return routeSingleSelectTab(kb, data, state, runtime);
2023
+ }
2024
+ __name(routeKey, "routeKey");
2025
+
2026
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts?ext=rpiv-ask-user-question
2027
+ function orderedAnswers(state, questions) {
2028
+ const out = [];
2029
+ for (let i = 0; i < questions.length; i++) {
2030
+ const a = state.answers.get(i);
2031
+ if (a) out.push(a);
2032
+ }
2033
+ return out;
2034
+ }
2035
+ __name(orderedAnswers, "orderedAnswers");
2036
+ function syncMultiSelectFromAnswers(answers, questions, tab) {
2037
+ const q = questions[tab];
2038
+ if (!q?.multiSelect) return /* @__PURE__ */ new Set();
2039
+ const saved = answers.get(tab);
2040
+ const labels = saved?.selected ?? [];
2041
+ const indices = /* @__PURE__ */ new Set();
2042
+ for (let i = 0; i < q.options.length; i++) {
2043
+ if (labels.includes(q.options[i].label)) indices.add(i);
2044
+ }
2045
+ return indices;
2046
+ }
2047
+ __name(syncMultiSelectFromAnswers, "syncMultiSelectFromAnswers");
2048
+ function selectedLabels(state, question) {
2049
+ const selected = [];
2050
+ for (let i = 0; i < question.options.length; i++) {
2051
+ if (state.multiSelectChecked.has(i)) selected.push(question.options[i].label);
2052
+ }
2053
+ return selected;
2054
+ }
2055
+ __name(selectedLabels, "selectedLabels");
2056
+ function persistMultiSelectAnswer(state, ctx) {
2057
+ const q = ctx.questions[state.currentTab];
2058
+ if (!q?.multiSelect) return state.answers;
2059
+ const selected = selectedLabels(state, q);
2060
+ const custom = customDraftValueFor(state, state.currentTab);
2061
+ const hasCustom = custom.trim().length > 0;
2062
+ const out = new Map(state.answers);
2063
+ if (selected.length === 0 && !hasCustom) {
2064
+ out.delete(state.currentTab);
2065
+ return out;
2066
+ }
2067
+ const pendingNotes = state.notesByTab.get(state.currentTab);
2068
+ out.set(state.currentTab, {
2069
+ questionIndex: state.currentTab,
2070
+ question: q.question,
2071
+ kind: "multi",
2072
+ answer: hasCustom ? custom : null,
2073
+ selected,
2074
+ ...pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}
2075
+ });
2076
+ return out;
2077
+ }
2078
+ __name(persistMultiSelectAnswer, "persistMultiSelectAnswer");
2079
+ function notesValueFor(state, tab) {
2080
+ return state.notesByTab.get(tab) ?? state.answers.get(tab)?.notes ?? "";
2081
+ }
2082
+ __name(notesValueFor, "notesValueFor");
2083
+ function customDraftValueFor(state, tab) {
2084
+ const draft = state.customDraftsByTab.get(tab);
2085
+ if (draft !== void 0) return draft;
2086
+ const answer = state.answers.get(tab);
2087
+ if (answer?.kind !== "custom" && answer?.kind !== "multi") return "";
2088
+ return typeof answer.answer === "string" ? answer.answer : "";
2089
+ }
2090
+ __name(customDraftValueFor, "customDraftValueFor");
2091
+ function setCustomDraft(state, tab, value) {
2092
+ const drafts = new Map(state.customDraftsByTab);
2093
+ drafts.set(tab, value);
2094
+ return drafts;
2095
+ }
2096
+ __name(setCustomDraft, "setCustomDraft");
2097
+ function withoutCustomDraft(state, tab) {
2098
+ if (!state.customDraftsByTab.has(tab)) return state.customDraftsByTab;
2099
+ const drafts = new Map(state.customDraftsByTab);
2100
+ drafts.delete(tab);
2101
+ return drafts;
2102
+ }
2103
+ __name(withoutCustomDraft, "withoutCustomDraft");
2104
+ function switchTabResult(state, nextTab, ctx) {
2105
+ const notesValue = notesValueFor(state, nextTab);
2106
+ const transitioned = {
2107
+ ...state,
2108
+ currentTab: nextTab,
2109
+ optionIndex: 0,
2110
+ inputMode: false,
2111
+ notesVisible: false,
2112
+ submitChoiceIndex: 0,
2113
+ multiSelectChecked: syncMultiSelectFromAnswers(state.answers, ctx.questions, nextTab),
2114
+ notesDraft: notesValue
2115
+ };
2116
+ return {
2117
+ state: transitioned,
2118
+ effects: [
2119
+ { kind: "set_notes_focused", focused: false },
2120
+ { kind: "set_notes_value", value: notesValue },
2121
+ { kind: "set_input_buffer", value: customDraftValueFor(state, nextTab) }
2122
+ ]
2123
+ };
2124
+ }
2125
+ __name(switchTabResult, "switchTabResult");
2126
+ function doneFor(state, ctx, cancelled) {
2127
+ const globalNote = state.notesByTab.get(ctx.questions.length);
2128
+ const result = {
2129
+ answers: orderedAnswers(state, ctx.questions),
2130
+ cancelled,
2131
+ ...globalNote && globalNote.length > 0 ? { globalNote } : {}
2132
+ };
2133
+ return { state, effects: [{ kind: "done", result }] };
2134
+ }
2135
+ __name(doneFor, "doneFor");
2136
+ var navHandler = /* @__PURE__ */ __name((state, action, ctx) => {
2137
+ const items = ctx.itemsByTab[state.currentTab] ?? [];
2138
+ const item = items[action.nextIndex];
2139
+ const inputMode = item ? ROW_INTENT_META[item.kind].activatesInputMode : false;
2140
+ const customDraftsByTab = state.inputMode ? setCustomDraft(state, state.currentTab, action.inputValue) : state.customDraftsByTab;
2141
+ let next = { ...state, optionIndex: action.nextIndex, inputMode, customDraftsByTab };
2142
+ if (state.inputMode && !inputMode && ctx.questions[state.currentTab]?.multiSelect) {
2143
+ next = { ...next, answers: persistMultiSelectAnswer(next, ctx) };
2144
+ }
2145
+ if (!inputMode) return { state: next, effects: [] };
2146
+ return {
2147
+ state: next,
2148
+ effects: [{ kind: "set_input_buffer", value: customDraftValueFor(next, state.currentTab) }]
2149
+ };
2150
+ }, "navHandler");
2151
+ var inputClearHandler = /* @__PURE__ */ __name((state, _action, _ctx) => ({
2152
+ state: { ...state, customDraftsByTab: setCustomDraft(state, state.currentTab, "") },
2153
+ effects: [{ kind: "clear_input_buffer" }]
2154
+ }), "inputClearHandler");
2155
+ var inputEditHandler = /* @__PURE__ */ __name((state, action, _ctx) => ({
2156
+ state,
2157
+ effects: [{ kind: "open_input_editor", value: action.value }]
2158
+ }), "inputEditHandler");
2159
+ var inputReplaceHandler = /* @__PURE__ */ __name((state, action, _ctx) => ({
2160
+ state: { ...state, customDraftsByTab: setCustomDraft(state, state.currentTab, action.value) },
2161
+ effects: [{ kind: "set_input_buffer", value: action.value }]
2162
+ }), "inputReplaceHandler");
2163
+ var tabSwitchHandler = /* @__PURE__ */ __name((state, action, ctx) => switchTabResult(state, action.nextTab, ctx), "tabSwitchHandler");
2164
+ var confirmHandler = /* @__PURE__ */ __name((state, action, ctx) => {
2165
+ let answer = action.answer;
2166
+ const q = ctx.questions[answer.questionIndex];
2167
+ if (answer.kind === "custom" && q?.multiSelect) {
2168
+ answer = { ...answer, kind: "multi", selected: selectedLabels(state, q) };
2169
+ } else if (answer.kind === "option" && answer.answer) {
2170
+ const matched = q?.options.find((o) => o.label === answer.answer);
2171
+ if (matched?.preview && matched.preview.length > 0) {
2172
+ answer = { ...answer, preview: matched.preview };
2173
+ }
2174
+ }
2175
+ const pendingNotes = state.notesByTab.get(answer.questionIndex);
2176
+ if (pendingNotes && pendingNotes.length > 0) {
2177
+ answer = { ...answer, notes: pendingNotes };
2178
+ }
2179
+ const answers = new Map(state.answers);
2180
+ answers.set(answer.questionIndex, answer);
2181
+ const hasCustomAnswer = answer.kind === "custom" || answer.kind === "multi" && answer.answer !== null;
2182
+ const customDraftsByTab = hasCustomAnswer ? withoutCustomDraft(state, answer.questionIndex) : state.customDraftsByTab;
2183
+ const next = {
2184
+ ...state,
2185
+ answers,
2186
+ customDraftsByTab
2187
+ };
2188
+ if (action.autoAdvanceTab !== void 0) return switchTabResult(next, action.autoAdvanceTab, ctx);
2189
+ return doneFor(next, ctx, false);
2190
+ }, "confirmHandler");
2191
+ var toggleHandler = /* @__PURE__ */ __name((state, action, ctx) => {
2192
+ const checked = new Set(state.multiSelectChecked);
2193
+ if (checked.has(action.index)) checked.delete(action.index);
2194
+ else checked.add(action.index);
2195
+ const intermediate = { ...state, multiSelectChecked: checked };
2196
+ const answers = persistMultiSelectAnswer(intermediate, ctx);
2197
+ return { state: { ...intermediate, answers }, effects: [] };
2198
+ }, "toggleHandler");
2199
+ var multiConfirmHandler = /* @__PURE__ */ __name((state, action, ctx) => {
2200
+ const q = ctx.questions[state.currentTab];
2201
+ if (!q) return { state, effects: [] };
2202
+ const pendingNotes = state.notesByTab.get(state.currentTab);
2203
+ const answers = new Map(state.answers);
2204
+ answers.set(state.currentTab, {
2205
+ questionIndex: state.currentTab,
2206
+ question: q.question,
2207
+ kind: "multi",
2208
+ answer: action.answer,
2209
+ selected: action.selected,
2210
+ ...pendingNotes && pendingNotes.length > 0 ? { notes: pendingNotes } : {}
2211
+ });
2212
+ const synced = {
2213
+ ...state,
2214
+ answers,
2215
+ customDraftsByTab: withoutCustomDraft(state, state.currentTab),
2216
+ multiSelectChecked: syncMultiSelectFromAnswers(answers, ctx.questions, state.currentTab)
2217
+ };
2218
+ if (action.autoAdvanceTab !== void 0) return switchTabResult(synced, action.autoAdvanceTab, ctx);
2219
+ return doneFor(synced, ctx, false);
2220
+ }, "multiConfirmHandler");
2221
+ var notesEnterHandler = /* @__PURE__ */ __name((state, _action, _ctx) => {
2222
+ const value = notesValueFor(state, state.currentTab);
2223
+ return {
2224
+ state: { ...state, notesVisible: true, notesDraft: value },
2225
+ effects: [
2226
+ { kind: "set_notes_value", value },
2227
+ { kind: "set_notes_focused", focused: true }
2228
+ ]
2229
+ };
2230
+ }, "notesEnterHandler");
2231
+ var notesExitHandler = /* @__PURE__ */ __name((state, _action, _ctx) => {
2232
+ const trimmed = state.notesDraft.trim();
2233
+ const notes = new Map(state.notesByTab);
2234
+ const answers = new Map(state.answers);
2235
+ if (trimmed.length === 0) {
2236
+ notes.delete(state.currentTab);
2237
+ const prev = answers.get(state.currentTab);
2238
+ if (prev?.notes) {
2239
+ const stripped = { ...prev };
2240
+ delete stripped.notes;
2241
+ answers.set(state.currentTab, stripped);
2242
+ }
2243
+ } else {
2244
+ notes.set(state.currentTab, trimmed);
2245
+ const prev = answers.get(state.currentTab);
2246
+ if (prev) answers.set(state.currentTab, { ...prev, notes: trimmed });
2247
+ }
2248
+ return {
2249
+ state: { ...state, notesByTab: notes, answers, notesVisible: false },
2250
+ effects: [{ kind: "set_notes_focused", focused: false }]
2251
+ };
2252
+ }, "notesExitHandler");
2253
+ var cancelHandler = /* @__PURE__ */ __name((s, _a, c) => doneFor(s, c, true), "cancelHandler");
2254
+ var submitHandler = /* @__PURE__ */ __name((s, _a, c) => doneFor(s, c, false), "submitHandler");
2255
+ var submitNavHandler = /* @__PURE__ */ __name((s, a, _c) => ({
2256
+ state: { ...s, submitChoiceIndex: a.nextIndex },
2257
+ effects: []
2258
+ }), "submitNavHandler");
2259
+ var notesForwardHandler = /* @__PURE__ */ __name((s, a, _c) => ({
2260
+ state: s,
2261
+ effects: [{ kind: "forward_notes_keystroke", data: a.data }]
2262
+ }), "notesForwardHandler");
2263
+ var toggleCollapsedHandler = /* @__PURE__ */ __name((s, _a, _c) => ({
2264
+ state: { ...s, collapsed: !s.collapsed },
2265
+ effects: [{ kind: "set_overlay_hidden", hidden: !s.collapsed }]
2266
+ }), "toggleCollapsedHandler");
2267
+ var ignoreHandler = /* @__PURE__ */ __name((s, _a, _c) => ({ state: s, effects: [] }), "ignoreHandler");
2268
+ var HANDLERS = {
2269
+ nav: navHandler,
2270
+ input_clear: inputClearHandler,
2271
+ input_edit: inputEditHandler,
2272
+ input_replace: inputReplaceHandler,
2273
+ tab_switch: tabSwitchHandler,
2274
+ confirm: confirmHandler,
2275
+ toggle: toggleHandler,
2276
+ multi_confirm: multiConfirmHandler,
2277
+ cancel: cancelHandler,
2278
+ notes_enter: notesEnterHandler,
2279
+ notes_exit: notesExitHandler,
2280
+ notes_forward: notesForwardHandler,
2281
+ submit: submitHandler,
2282
+ submit_nav: submitNavHandler,
2283
+ toggle_collapsed: toggleCollapsedHandler,
2284
+ ignore: ignoreHandler
2285
+ };
2286
+ function reduce(state, action, ctx) {
2287
+ const handler = HANDLERS[action.kind];
2288
+ return handler(state, action, ctx);
2289
+ }
2290
+ __name(reduce, "reduce");
2291
+
2292
+ // node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts?ext=rpiv-ask-user-question
2293
+ function initialState() {
2294
+ return {
2295
+ currentTab: 0,
2296
+ optionIndex: 0,
2297
+ inputMode: false,
2298
+ notesVisible: false,
2299
+ answers: /* @__PURE__ */ new Map(),
2300
+ multiSelectChecked: /* @__PURE__ */ new Set(),
2301
+ customDraftsByTab: /* @__PURE__ */ new Map(),
2302
+ notesByTab: /* @__PURE__ */ new Map(),
2303
+ submitChoiceIndex: 0,
2304
+ notesDraft: "",
2305
+ collapsed: false
2306
+ };
2307
+ }
2308
+ __name(initialState, "initialState");
2309
+ var QuestionnaireSession = class {
2310
+ static {
2311
+ __name(this, "QuestionnaireSession");
2312
+ }
2313
+ state = initialState();
2314
+ questions;
2315
+ isMulti;
2316
+ itemsByTab;
2317
+ notesInput;
2318
+ inlineInput;
2319
+ viewAdapter;
2320
+ keybindings;
2321
+ editInput;
2322
+ collapseKey;
2323
+ canReopenWhileHidden;
2324
+ inputEditorOpen = false;
2325
+ /**
2326
+ * Overlay handle captured by `ctx.ui.custom`'s `onHandle` callback. Lets the session
2327
+ * call `setHidden(true/false)` so pi-tui's overlay stack reflects the collapsed state
2328
+ * and overlay-aware consumers (e.g. `pi-station`) can resume normal behaviour.
2329
+ */
2330
+ overlayHandle;
2331
+ tui;
2332
+ done;
2333
+ component;
2334
+ constructor(config) {
2335
+ this.tui = config.tui;
2336
+ this.done = config.done;
2337
+ this.questions = config.params.questions;
2338
+ this.isMulti = this.questions.length > 1;
2339
+ this.itemsByTab = config.itemsByTab;
2340
+ this.keybindings = config.keybindings;
2341
+ this.editInput = config.editInput;
2342
+ this.collapseKey = config.collapseKey;
2343
+ this.canReopenWhileHidden = config.canReopenWhileHidden;
2344
+ const built = buildQuestionnaire({
2345
+ tui: this.tui,
2346
+ theme: config.theme,
2347
+ questions: this.questions,
2348
+ itemsByTab: this.itemsByTab,
2349
+ isMulti: this.isMulti,
2350
+ initialState: this.state,
2351
+ getCurrentTab: /* @__PURE__ */ __name(() => this.state.currentTab, "getCurrentTab"),
2352
+ collapseKey: this.collapseKey,
2353
+ rowBudget: config.rowBudget
2354
+ });
2355
+ this.notesInput = built.notesInput;
2356
+ this.inlineInput = built.inlineInput;
2357
+ this.viewAdapter = built.adapter;
2358
+ this.component = this.assembleComponent(built, config.theme);
2359
+ this.viewAdapter.apply(this.state);
2360
+ }
2361
+ assembleComponent(built, theme) {
2362
+ const collapsedRender = this.buildCollapsedRender(theme);
2363
+ return {
2364
+ render: /* @__PURE__ */ __name((width) => this.state.collapsed ? collapsedRender(width) : built.render(width), "render"),
2365
+ invalidate: built.invalidate,
2366
+ handleInput: /* @__PURE__ */ __name((data) => this.dispatch(data), "handleInput")
2367
+ };
2368
+ }
2369
+ /**
2370
+ * Collapsed render: a single dim row (inline presentation: in the editor slot, where it
2371
+ * keeps focus so the same key expands it). For the overlay, pi-tui sizes
2372
+ * the overlay to `min(lines.length, maxHeight)`, so returning one line shrinks
2373
+ * the bottom-anchored overlay from full-height to one row and the transcript
2374
+ * behind it becomes readable (#47). The overlay stays focused and in the
2375
+ * stack, so the collapse key still routes here to expand. `t` stays inside the
2376
+ * closure (live locale updates); the key display is static per session.
2377
+ *
2378
+ * With collapseKey "off" the router and raw listener never toggle `collapsed`,
2379
+ * but `toggleCollapsedExternal()` is a public ungated entry — fall back to the
2380
+ * cancel-only line rather than rendering a literal "Off to expand".
2381
+ */
2382
+ buildCollapsedRender(theme) {
2383
+ const collapseKeyDisplay = formatKeySpecForDisplay(this.collapseKey);
2384
+ const collapsedHintLine = /* @__PURE__ */ __name(() => this.collapseKey === COLLAPSE_KEY_OFF ? t("hint.cancel", HINT_PART_CANCEL) : t("hint.expand_line", COLLAPSED_HINT_TEMPLATE).replace(KEY_PLACEHOLDER, collapseKeyDisplay), "collapsedHintLine");
2385
+ return (_width) => [theme.fg("dim", ` ${collapsedHintLine()} `)];
2386
+ }
2387
+ dispatch(data) {
2388
+ if (this.inputEditorOpen) return;
2389
+ const action = routeKey(data, this.state, this.runtime());
2390
+ if (action.kind === "ignore") {
2391
+ this.handleIgnoreInline(data);
2392
+ return;
2393
+ }
2394
+ this.commit(action);
2395
+ }
2396
+ commit(action) {
2397
+ const result = reduce(this.state, action, this.applyContext());
2398
+ this.state = result.state;
2399
+ for (const effect of result.effects) this.runEffect(effect);
2400
+ this.state = this.mirrorNotesDraft(this.state);
2401
+ this.viewAdapter.apply(this.state);
2402
+ }
2403
+ mirrorNotesDraft(s) {
2404
+ const draft = this.notesInput.getExpandedText?.() ?? this.notesInput.getText();
2405
+ return s.notesDraft === draft ? s : { ...s, notesDraft: draft };
2406
+ }
2407
+ runEffect(effect) {
2408
+ switch (effect.kind) {
2409
+ case "set_input_buffer":
2410
+ this.inlineInput.setText(effect.value);
2411
+ return;
2412
+ case "clear_input_buffer":
2413
+ this.inlineInput.setText("");
2414
+ return;
2415
+ case "open_input_editor":
2416
+ this.openInputEditorAsync(effect.value);
2417
+ return;
2418
+ case "set_notes_value":
2419
+ this.notesInput.setText(effect.value);
2420
+ return;
2421
+ case "set_notes_focused":
2422
+ this.notesInput.focused = effect.focused;
2423
+ return;
2424
+ case "forward_notes_keystroke":
2425
+ this.notesInput.handleInput(effect.data);
2426
+ return;
2427
+ case "set_overlay_hidden":
2428
+ if (this.canReopenWhileHidden) this.overlayHandle?.setHidden(effect.hidden);
2429
+ return;
2430
+ case "done":
2431
+ this.done(effect.result);
2432
+ return;
2433
+ }
2434
+ }
2435
+ /** Opens Pi's configured external editor; on success commits the replacement buffer, on reported failure retains the draft. */
2436
+ openInputEditorAsync(value) {
2437
+ if (this.inputEditorOpen) return;
2438
+ this.inputEditorOpen = true;
2439
+ void this.editInput(value).then(
2440
+ (edited) => {
2441
+ this.inputEditorOpen = false;
2442
+ if (edited !== void 0) this.commit({ kind: "input_replace", value: edited });
2443
+ },
2444
+ () => {
2445
+ this.inputEditorOpen = false;
2446
+ }
2447
+ );
2448
+ }
2449
+ /**
2450
+ * Per-keystroke `ignore` fast path: delegates text editing to Pi's headless
2451
+ * multiline `Editor`, including paste, undo, cursor movement, and configured
2452
+ * `tui.input.newLine` handling. `viewAdapter.apply` then projects its public
2453
+ * text/cursor state without a reducer round-trip.
2454
+ */
2455
+ handleIgnoreInline(data) {
2456
+ if (!this.state.inputMode) return;
2457
+ this.inlineInput.handleInput(data);
2458
+ this.viewAdapter.apply(this.state);
2459
+ }
2460
+ runtime() {
2461
+ const cursor = this.inlineInput.getCursor();
2462
+ const lastLine = this.inlineInput.getLines().length - 1;
2463
+ return {
2464
+ keybindings: this.keybindings,
2465
+ inputBuffer: this.inlineInput.getExpandedText?.() ?? this.inlineInput.getText(),
2466
+ canMoveInputUp: cursor.line > 0,
2467
+ canMoveInputDown: cursor.line < lastLine,
2468
+ questions: this.questions,
2469
+ isMulti: this.isMulti,
2470
+ currentItem: this.currentItem(),
2471
+ items: this.itemsByTab[this.state.currentTab] ?? [],
2472
+ collapseKey: this.collapseKey
2473
+ };
2474
+ }
2475
+ applyContext() {
2476
+ return {
2477
+ questions: this.questions,
2478
+ itemsByTab: this.itemsByTab
2479
+ };
2480
+ }
2481
+ currentItem() {
2482
+ const arr = this.itemsByTab[this.state.currentTab] ?? [];
2483
+ return this.state.optionIndex < arr.length ? arr[this.state.optionIndex] : void 0;
2484
+ }
2485
+ /**
2486
+ * Setter for the overlay handle, called by `ctx.ui.custom`'s `onHandle` callback once
2487
+ * the TUI has created the overlay. Until this is called, `set_overlay_hidden` effects
2488
+ * are no-ops — the session still tracks `state.collapsed` for the view layer.
2489
+ */
2490
+ setOverlayHandle(handle) {
2491
+ this.overlayHandle = handle;
2492
+ }
2493
+ /**
2494
+ * Public toggle used by the raw terminal input listener registered in `execute()`.
2495
+ * pi-tui does not route input to a hidden overlay's `component.handleInput`, so the
2496
+ * raw listener (which fires for terminal data regardless of overlay visibility)
2497
+ * reaches the session through this method instead of the dispatch path. Routed
2498
+ * through `commit` so the transition stays in the reducer and the overlay hide
2499
+ * happens via the `set_overlay_hidden` effect like every other side effect.
2500
+ */
2501
+ toggleCollapsedExternal() {
2502
+ if (!this.inputEditorOpen) this.commit({ kind: "toggle_collapsed" });
2503
+ }
2504
+ };
2505
+ export {
2506
+ QuestionnaireSession
2507
+ };