@buoy-gg/ask-buoy 7.0.36 → 7.0.38

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 (312) hide show
  1. package/README.md +58 -27
  2. package/lib/commonjs/blocks/AskBlocks.js +1 -338
  3. package/lib/commonjs/blocks/BlockRenderer.js +1 -262
  4. package/lib/commonjs/blocks/ChromePrototypes.js +1 -514
  5. package/lib/commonjs/blocks/DataBlocks.js +1 -781
  6. package/lib/commonjs/blocks/ExtraBlocks.js +1 -1860
  7. package/lib/commonjs/blocks/MediaBlocks.js +1 -156
  8. package/lib/commonjs/blocks/ReceiptBlocks.js +1 -586
  9. package/lib/commonjs/blocks/blockText.js +1 -60
  10. package/lib/commonjs/blocks/blockText.test.js +1 -53
  11. package/lib/commonjs/blocks/context.js +1 -42
  12. package/lib/commonjs/blocks/icons.js +1 -96
  13. package/lib/commonjs/blocks/index.js +1 -95
  14. package/lib/commonjs/blocks/listRow.test.js +1 -55
  15. package/lib/commonjs/blocks/pageState.js +1 -34
  16. package/lib/commonjs/blocks/pageState.test.js +1 -57
  17. package/lib/commonjs/blocks/primitives.js +1 -457
  18. package/lib/commonjs/blocks/primitivesIndex.js +1 -62
  19. package/lib/commonjs/components/ApprovalCard.js +1 -244
  20. package/lib/commonjs/components/AskBuoyHeader.js +1 -196
  21. package/lib/commonjs/components/AskBuoyIcon.js +1 -19
  22. package/lib/commonjs/components/AskBuoyModal.js +1 -479
  23. package/lib/commonjs/components/AskBuoySettings.js +1 -119
  24. package/lib/commonjs/components/ChangesBar.js +1 -211
  25. package/lib/commonjs/components/Composer.js +1 -255
  26. package/lib/commonjs/components/EmptyState.js +1 -161
  27. package/lib/commonjs/components/KeyboardPad.js +1 -80
  28. package/lib/commonjs/components/Markdown.js +1 -310
  29. package/lib/commonjs/components/MessageList.js +1 -930
  30. package/lib/commonjs/components/QueuedMessages.js +1 -145
  31. package/lib/commonjs/components/ThinkingStrip.js +1 -223
  32. package/lib/commonjs/components/TypingDots.js +1 -63
  33. package/lib/commonjs/components/markdownParse.js +1 -131
  34. package/lib/commonjs/components/markdownParse.test.js +1 -99
  35. package/lib/commonjs/components/useCopyAction.js +1 -50
  36. package/lib/commonjs/index.js +1 -252
  37. package/lib/commonjs/preset.js +1 -90
  38. package/lib/commonjs/store/activityGroups.js +1 -110
  39. package/lib/commonjs/store/activityGroups.test.js +1 -78
  40. package/lib/commonjs/store/askBuoySettings.js +1 -72
  41. package/lib/commonjs/store/askBuoyStore.js +1 -547
  42. package/lib/commonjs/store/blockPlacement.js +1 -64
  43. package/lib/commonjs/store/blockPlacement.test.js +1 -123
  44. package/lib/commonjs/store/queue.test.js +1 -150
  45. package/lib/commonjs/store/resetConversation.js +1 -77
  46. package/lib/commonjs/store/restorePending.test.js +1 -63
  47. package/lib/commonjs/store/restoredIds.test.js +1 -47
  48. package/lib/commonjs/store/suggestions.js +1 -54
  49. package/lib/commonjs/store/suggestions.test.js +1 -58
  50. package/lib/commonjs/store/systemLine.test.js +1 -77
  51. package/lib/commonjs/store/ticketPrompt.js +1 -17
  52. package/lib/commonjs/store/traceText.js +1 -81
  53. package/lib/commonjs/store/traceText.test.js +1 -112
  54. package/lib/commonjs/store/transcriptPersistence.js +1 -297
  55. package/lib/commonjs/store/transcriptPersistence.test.js +1 -333
  56. package/lib/commonjs/store/turnRendering.test.js +1 -151
  57. package/lib/commonjs/store/useAskBuoy.js +1 -622
  58. package/lib/commonjs/store/useAskBuoySettings.js +1 -14
  59. package/lib/commonjs/sync/askBuoySyncAdapter.js +1 -140
  60. package/lib/commonjs/sync/askBuoySyncAdapter.test.js +1 -141
  61. package/lib/commonjs/sync/wire.js +1 -58
  62. package/lib/commonjs/types.js +1 -6
  63. package/lib/module/blocks/AskBlocks.js +1 -331
  64. package/lib/module/blocks/BlockRenderer.js +1 -257
  65. package/lib/module/blocks/ChromePrototypes.js +1 -503
  66. package/lib/module/blocks/DataBlocks.js +1 -767
  67. package/lib/module/blocks/ExtraBlocks.js +1 -1833
  68. package/lib/module/blocks/MediaBlocks.js +1 -151
  69. package/lib/module/blocks/ReceiptBlocks.js +1 -574
  70. package/lib/module/blocks/blockText.js +1 -55
  71. package/lib/module/blocks/blockText.test.js +1 -53
  72. package/lib/module/blocks/context.js +1 -35
  73. package/lib/module/blocks/icons.js +1 -91
  74. package/lib/module/blocks/index.js +1 -8
  75. package/lib/module/blocks/listRow.test.js +1 -56
  76. package/lib/module/blocks/pageState.js +1 -30
  77. package/lib/module/blocks/pageState.test.js +1 -56
  78. package/lib/module/blocks/primitives.js +1 -445
  79. package/lib/module/blocks/primitivesIndex.js +1 -5
  80. package/lib/module/components/ApprovalCard.js +1 -239
  81. package/lib/module/components/AskBuoyHeader.js +1 -193
  82. package/lib/module/components/AskBuoyIcon.js +1 -8
  83. package/lib/module/components/AskBuoyModal.js +1 -474
  84. package/lib/module/components/AskBuoySettings.js +1 -114
  85. package/lib/module/components/ChangesBar.js +1 -206
  86. package/lib/module/components/Composer.js +1 -250
  87. package/lib/module/components/EmptyState.js +1 -156
  88. package/lib/module/components/KeyboardPad.js +1 -75
  89. package/lib/module/components/Markdown.js +1 -305
  90. package/lib/module/components/MessageList.js +1 -924
  91. package/lib/module/components/QueuedMessages.js +1 -140
  92. package/lib/module/components/ThinkingStrip.js +1 -218
  93. package/lib/module/components/TypingDots.js +1 -58
  94. package/lib/module/components/markdownParse.js +1 -125
  95. package/lib/module/components/markdownParse.test.js +1 -98
  96. package/lib/module/components/useCopyAction.js +1 -46
  97. package/lib/module/index.js +1 -34
  98. package/lib/module/preset.js +1 -73
  99. package/lib/module/store/activityGroups.js +1 -103
  100. package/lib/module/store/activityGroups.test.js +1 -77
  101. package/lib/module/store/askBuoySettings.js +1 -67
  102. package/lib/module/store/askBuoyStore.js +1 -541
  103. package/lib/module/store/blockPlacement.js +1 -59
  104. package/lib/module/store/blockPlacement.test.js +1 -122
  105. package/lib/module/store/queue.test.js +1 -149
  106. package/lib/module/store/resetConversation.js +1 -72
  107. package/lib/module/store/restorePending.test.js +1 -62
  108. package/lib/module/store/restoredIds.test.js +1 -46
  109. package/lib/module/store/suggestions.js +1 -49
  110. package/lib/module/store/suggestions.test.js +1 -57
  111. package/lib/module/store/systemLine.test.js +1 -76
  112. package/lib/module/store/ticketPrompt.js +1 -13
  113. package/lib/module/store/traceText.js +1 -75
  114. package/lib/module/store/traceText.test.js +1 -111
  115. package/lib/module/store/transcriptPersistence.js +1 -287
  116. package/lib/module/store/transcriptPersistence.test.js +1 -332
  117. package/lib/module/store/turnRendering.test.js +1 -150
  118. package/lib/module/store/useAskBuoy.js +1 -616
  119. package/lib/module/store/useAskBuoySettings.js +1 -9
  120. package/lib/module/sync/askBuoySyncAdapter.js +1 -135
  121. package/lib/module/sync/askBuoySyncAdapter.test.js +1 -140
  122. package/lib/module/sync/wire.js +1 -47
  123. package/lib/module/types.js +1 -4
  124. package/lib/typescript/components/ApprovalCard.d.ts +9 -1
  125. package/lib/typescript/preset.d.ts +4 -2
  126. package/lib/typescript/store/askBuoyStore.d.ts +32 -4
  127. package/lib/typescript/store/useAskBuoy.d.ts +3 -2
  128. package/lib/typescript/sync/askBuoySyncAdapter.d.ts +3 -0
  129. package/package.json +9 -9
  130. package/lib/commonjs/blocks/AskBlocks.js.map +0 -1
  131. package/lib/commonjs/blocks/BlockRenderer.js.map +0 -1
  132. package/lib/commonjs/blocks/ChromePrototypes.js.map +0 -1
  133. package/lib/commonjs/blocks/DataBlocks.js.map +0 -1
  134. package/lib/commonjs/blocks/ExtraBlocks.js.map +0 -1
  135. package/lib/commonjs/blocks/MediaBlocks.js.map +0 -1
  136. package/lib/commonjs/blocks/ReceiptBlocks.js.map +0 -1
  137. package/lib/commonjs/blocks/blockText.js.map +0 -1
  138. package/lib/commonjs/blocks/blockText.test.js.map +0 -1
  139. package/lib/commonjs/blocks/context.js.map +0 -1
  140. package/lib/commonjs/blocks/icons.js.map +0 -1
  141. package/lib/commonjs/blocks/index.js.map +0 -1
  142. package/lib/commonjs/blocks/listRow.test.js.map +0 -1
  143. package/lib/commonjs/blocks/pageState.js.map +0 -1
  144. package/lib/commonjs/blocks/pageState.test.js.map +0 -1
  145. package/lib/commonjs/blocks/primitives.js.map +0 -1
  146. package/lib/commonjs/blocks/primitivesIndex.js.map +0 -1
  147. package/lib/commonjs/components/ApprovalCard.js.map +0 -1
  148. package/lib/commonjs/components/AskBuoyHeader.js.map +0 -1
  149. package/lib/commonjs/components/AskBuoyIcon.js.map +0 -1
  150. package/lib/commonjs/components/AskBuoyModal.js.map +0 -1
  151. package/lib/commonjs/components/AskBuoySettings.js.map +0 -1
  152. package/lib/commonjs/components/ChangesBar.js.map +0 -1
  153. package/lib/commonjs/components/Composer.js.map +0 -1
  154. package/lib/commonjs/components/EmptyState.js.map +0 -1
  155. package/lib/commonjs/components/KeyboardPad.js.map +0 -1
  156. package/lib/commonjs/components/Markdown.js.map +0 -1
  157. package/lib/commonjs/components/MessageList.js.map +0 -1
  158. package/lib/commonjs/components/QueuedMessages.js.map +0 -1
  159. package/lib/commonjs/components/ThinkingStrip.js.map +0 -1
  160. package/lib/commonjs/components/TypingDots.js.map +0 -1
  161. package/lib/commonjs/components/markdownParse.js.map +0 -1
  162. package/lib/commonjs/components/markdownParse.test.js.map +0 -1
  163. package/lib/commonjs/components/useCopyAction.js.map +0 -1
  164. package/lib/commonjs/index.js.map +0 -1
  165. package/lib/commonjs/preset.js.map +0 -1
  166. package/lib/commonjs/store/activityGroups.js.map +0 -1
  167. package/lib/commonjs/store/activityGroups.test.js.map +0 -1
  168. package/lib/commonjs/store/askBuoySettings.js.map +0 -1
  169. package/lib/commonjs/store/askBuoyStore.js.map +0 -1
  170. package/lib/commonjs/store/blockPlacement.js.map +0 -1
  171. package/lib/commonjs/store/blockPlacement.test.js.map +0 -1
  172. package/lib/commonjs/store/queue.test.js.map +0 -1
  173. package/lib/commonjs/store/resetConversation.js.map +0 -1
  174. package/lib/commonjs/store/restorePending.test.js.map +0 -1
  175. package/lib/commonjs/store/restoredIds.test.js.map +0 -1
  176. package/lib/commonjs/store/suggestions.js.map +0 -1
  177. package/lib/commonjs/store/suggestions.test.js.map +0 -1
  178. package/lib/commonjs/store/systemLine.test.js.map +0 -1
  179. package/lib/commonjs/store/ticketPrompt.js.map +0 -1
  180. package/lib/commonjs/store/traceText.js.map +0 -1
  181. package/lib/commonjs/store/traceText.test.js.map +0 -1
  182. package/lib/commonjs/store/transcriptPersistence.js.map +0 -1
  183. package/lib/commonjs/store/transcriptPersistence.test.js.map +0 -1
  184. package/lib/commonjs/store/turnRendering.test.js.map +0 -1
  185. package/lib/commonjs/store/useAskBuoy.js.map +0 -1
  186. package/lib/commonjs/store/useAskBuoySettings.js.map +0 -1
  187. package/lib/commonjs/sync/askBuoySyncAdapter.js.map +0 -1
  188. package/lib/commonjs/sync/askBuoySyncAdapter.test.js.map +0 -1
  189. package/lib/commonjs/sync/wire.js.map +0 -1
  190. package/lib/commonjs/types.js.map +0 -1
  191. package/lib/module/blocks/AskBlocks.js.map +0 -1
  192. package/lib/module/blocks/BlockRenderer.js.map +0 -1
  193. package/lib/module/blocks/ChromePrototypes.js.map +0 -1
  194. package/lib/module/blocks/DataBlocks.js.map +0 -1
  195. package/lib/module/blocks/ExtraBlocks.js.map +0 -1
  196. package/lib/module/blocks/MediaBlocks.js.map +0 -1
  197. package/lib/module/blocks/ReceiptBlocks.js.map +0 -1
  198. package/lib/module/blocks/blockText.js.map +0 -1
  199. package/lib/module/blocks/blockText.test.js.map +0 -1
  200. package/lib/module/blocks/context.js.map +0 -1
  201. package/lib/module/blocks/icons.js.map +0 -1
  202. package/lib/module/blocks/index.js.map +0 -1
  203. package/lib/module/blocks/listRow.test.js.map +0 -1
  204. package/lib/module/blocks/pageState.js.map +0 -1
  205. package/lib/module/blocks/pageState.test.js.map +0 -1
  206. package/lib/module/blocks/primitives.js.map +0 -1
  207. package/lib/module/blocks/primitivesIndex.js.map +0 -1
  208. package/lib/module/components/ApprovalCard.js.map +0 -1
  209. package/lib/module/components/AskBuoyHeader.js.map +0 -1
  210. package/lib/module/components/AskBuoyIcon.js.map +0 -1
  211. package/lib/module/components/AskBuoyModal.js.map +0 -1
  212. package/lib/module/components/AskBuoySettings.js.map +0 -1
  213. package/lib/module/components/ChangesBar.js.map +0 -1
  214. package/lib/module/components/Composer.js.map +0 -1
  215. package/lib/module/components/EmptyState.js.map +0 -1
  216. package/lib/module/components/KeyboardPad.js.map +0 -1
  217. package/lib/module/components/Markdown.js.map +0 -1
  218. package/lib/module/components/MessageList.js.map +0 -1
  219. package/lib/module/components/QueuedMessages.js.map +0 -1
  220. package/lib/module/components/ThinkingStrip.js.map +0 -1
  221. package/lib/module/components/TypingDots.js.map +0 -1
  222. package/lib/module/components/markdownParse.js.map +0 -1
  223. package/lib/module/components/markdownParse.test.js.map +0 -1
  224. package/lib/module/components/useCopyAction.js.map +0 -1
  225. package/lib/module/index.js.map +0 -1
  226. package/lib/module/preset.js.map +0 -1
  227. package/lib/module/store/activityGroups.js.map +0 -1
  228. package/lib/module/store/activityGroups.test.js.map +0 -1
  229. package/lib/module/store/askBuoySettings.js.map +0 -1
  230. package/lib/module/store/askBuoyStore.js.map +0 -1
  231. package/lib/module/store/blockPlacement.js.map +0 -1
  232. package/lib/module/store/blockPlacement.test.js.map +0 -1
  233. package/lib/module/store/queue.test.js.map +0 -1
  234. package/lib/module/store/resetConversation.js.map +0 -1
  235. package/lib/module/store/restorePending.test.js.map +0 -1
  236. package/lib/module/store/restoredIds.test.js.map +0 -1
  237. package/lib/module/store/suggestions.js.map +0 -1
  238. package/lib/module/store/suggestions.test.js.map +0 -1
  239. package/lib/module/store/systemLine.test.js.map +0 -1
  240. package/lib/module/store/ticketPrompt.js.map +0 -1
  241. package/lib/module/store/traceText.js.map +0 -1
  242. package/lib/module/store/traceText.test.js.map +0 -1
  243. package/lib/module/store/transcriptPersistence.js.map +0 -1
  244. package/lib/module/store/transcriptPersistence.test.js.map +0 -1
  245. package/lib/module/store/turnRendering.test.js.map +0 -1
  246. package/lib/module/store/useAskBuoy.js.map +0 -1
  247. package/lib/module/store/useAskBuoySettings.js.map +0 -1
  248. package/lib/module/sync/askBuoySyncAdapter.js.map +0 -1
  249. package/lib/module/sync/askBuoySyncAdapter.test.js.map +0 -1
  250. package/lib/module/sync/wire.js.map +0 -1
  251. package/lib/module/types.js.map +0 -1
  252. package/lib/typescript/blocks/AskBlocks.d.ts.map +0 -1
  253. package/lib/typescript/blocks/BlockRenderer.d.ts.map +0 -1
  254. package/lib/typescript/blocks/ChromePrototypes.d.ts.map +0 -1
  255. package/lib/typescript/blocks/DataBlocks.d.ts.map +0 -1
  256. package/lib/typescript/blocks/ExtraBlocks.d.ts.map +0 -1
  257. package/lib/typescript/blocks/MediaBlocks.d.ts.map +0 -1
  258. package/lib/typescript/blocks/ReceiptBlocks.d.ts.map +0 -1
  259. package/lib/typescript/blocks/blockText.d.ts.map +0 -1
  260. package/lib/typescript/blocks/blockText.test.d.ts.map +0 -1
  261. package/lib/typescript/blocks/context.d.ts.map +0 -1
  262. package/lib/typescript/blocks/icons.d.ts.map +0 -1
  263. package/lib/typescript/blocks/index.d.ts.map +0 -1
  264. package/lib/typescript/blocks/listRow.test.d.ts.map +0 -1
  265. package/lib/typescript/blocks/pageState.d.ts.map +0 -1
  266. package/lib/typescript/blocks/pageState.test.d.ts.map +0 -1
  267. package/lib/typescript/blocks/primitives.d.ts.map +0 -1
  268. package/lib/typescript/blocks/primitivesIndex.d.ts.map +0 -1
  269. package/lib/typescript/components/ApprovalCard.d.ts.map +0 -1
  270. package/lib/typescript/components/AskBuoyHeader.d.ts.map +0 -1
  271. package/lib/typescript/components/AskBuoyIcon.d.ts.map +0 -1
  272. package/lib/typescript/components/AskBuoyModal.d.ts.map +0 -1
  273. package/lib/typescript/components/AskBuoySettings.d.ts.map +0 -1
  274. package/lib/typescript/components/ChangesBar.d.ts.map +0 -1
  275. package/lib/typescript/components/Composer.d.ts.map +0 -1
  276. package/lib/typescript/components/EmptyState.d.ts.map +0 -1
  277. package/lib/typescript/components/KeyboardPad.d.ts.map +0 -1
  278. package/lib/typescript/components/Markdown.d.ts.map +0 -1
  279. package/lib/typescript/components/MessageList.d.ts.map +0 -1
  280. package/lib/typescript/components/QueuedMessages.d.ts.map +0 -1
  281. package/lib/typescript/components/ThinkingStrip.d.ts.map +0 -1
  282. package/lib/typescript/components/TypingDots.d.ts.map +0 -1
  283. package/lib/typescript/components/markdownParse.d.ts.map +0 -1
  284. package/lib/typescript/components/markdownParse.test.d.ts.map +0 -1
  285. package/lib/typescript/components/useCopyAction.d.ts.map +0 -1
  286. package/lib/typescript/index.d.ts.map +0 -1
  287. package/lib/typescript/preset.d.ts.map +0 -1
  288. package/lib/typescript/store/activityGroups.d.ts.map +0 -1
  289. package/lib/typescript/store/activityGroups.test.d.ts.map +0 -1
  290. package/lib/typescript/store/askBuoySettings.d.ts.map +0 -1
  291. package/lib/typescript/store/askBuoyStore.d.ts.map +0 -1
  292. package/lib/typescript/store/blockPlacement.d.ts.map +0 -1
  293. package/lib/typescript/store/blockPlacement.test.d.ts.map +0 -1
  294. package/lib/typescript/store/queue.test.d.ts.map +0 -1
  295. package/lib/typescript/store/resetConversation.d.ts.map +0 -1
  296. package/lib/typescript/store/restorePending.test.d.ts.map +0 -1
  297. package/lib/typescript/store/restoredIds.test.d.ts.map +0 -1
  298. package/lib/typescript/store/suggestions.d.ts.map +0 -1
  299. package/lib/typescript/store/suggestions.test.d.ts.map +0 -1
  300. package/lib/typescript/store/systemLine.test.d.ts.map +0 -1
  301. package/lib/typescript/store/ticketPrompt.d.ts.map +0 -1
  302. package/lib/typescript/store/traceText.d.ts.map +0 -1
  303. package/lib/typescript/store/traceText.test.d.ts.map +0 -1
  304. package/lib/typescript/store/transcriptPersistence.d.ts.map +0 -1
  305. package/lib/typescript/store/transcriptPersistence.test.d.ts.map +0 -1
  306. package/lib/typescript/store/turnRendering.test.d.ts.map +0 -1
  307. package/lib/typescript/store/useAskBuoy.d.ts.map +0 -1
  308. package/lib/typescript/store/useAskBuoySettings.d.ts.map +0 -1
  309. package/lib/typescript/sync/askBuoySyncAdapter.d.ts.map +0 -1
  310. package/lib/typescript/sync/askBuoySyncAdapter.test.d.ts.map +0 -1
  311. package/lib/typescript/sync/wire.d.ts.map +0 -1
  312. package/lib/typescript/types.d.ts.map +0 -1
@@ -1,924 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * The transcript.
5
- *
6
- * Owns the ONLY scroller in the sheet. JsModal's default wrapper uses
7
- * `contentContainerStyle: { flexGrow: 1 }`, so a `flex: 1` scroller nested
8
- * inside resolves against the CONTENT rather than the viewport: it grows to
9
- * full content height, never scrolls, and the outer wrapper scrolls instead.
10
- * That is invisible to a human and fatal to Detox ("View is not scrollable at
11
- * the given start point"). The sheet passes `disableScrollWrapper` and this
12
- * list is the one scroller.
13
- *
14
- * Layout, and why:
15
- * - The user's messages are bubbles on the right; the assistant's text is
16
- * plain, full-width prose on the left. A grey bubble around every answer
17
- * cost 28pt of width per line and made the two sides look like the same
18
- * speaker.
19
- * - Tool calls render as an ACTIVITY CARD above the answer they produced —
20
- * one row per call, a status mark, a short human title ("Open /bag") and a
21
- * result word ("Done", "12 items", "Not found"). Nothing in a row is
22
- * truncated to the point of being unreadable: the title wraps, and tapping
23
- * the row expands it to the catalog's full sentence plus the tool it ran on.
24
- * The previous version showed the model-facing sentence cut off at two
25
- * lines with the result's JSON key names beside it, which was unreadable
26
- * AND uninformative.
27
- * - What a READ step returned (the receipt — "10 cached queries", a store's
28
- * key/values) lives INSIDE that step's row and shows only when the row is
29
- * expanded. Rendered as a standalone card under the activity list it read
30
- * as part of the answer: a question about one Pokémon produced a wall of
31
- * ten cache entries that a non-technical tester had no way to relate to
32
- * what they asked. The step row already says "10 items"; tapping it is the
33
- * ask for the detail. Receipts of WRITES (a diff of what changed) are drawn
34
- * in full — that is the thing the user must see without asking.
35
- * - CARDS COME LAST, AFTER THE PROSE. A turn reads activity → explanation →
36
- * the thing itself, which is the order a person would say it in. It used to
37
- * be the other way round, and on a long turn the answer — a grid of 28 shop
38
- * items — landed at the TOP with five paragraphs of narration underneath it,
39
- * so the finished answer arrived before the sentence explaining it. Ask
40
- * blocks (choice/confirm/input) stay last of all: they are the question the
41
- * turn ends on.
42
- */
43
- import { memo, useCallback, useEffect, useRef, useState } from "react";
44
- import { ActivityIndicator, FlatList, Pressable, StyleSheet, Text, View } from "react-native";
45
- import { AlertTriangle, Check, ChevronDown, ChevronRight, Copy, night, X } from "@buoy-gg/shared-ui";
46
- import { toolTitle } from "@buoy-gg/agent-core";
47
- import { BlockRenderer } from "../blocks/BlockRenderer";
48
- import { blocksToText } from "../blocks/blockText";
49
- import { groupCalls, turnFooter } from "../store/activityGroups";
50
- import { placeBlocks } from "../store/blockPlacement";
51
- import { askBuoyStore } from "../store/askBuoyStore";
52
- import { Markdown } from "./Markdown";
53
- import { ThinkingStrip } from "./ThinkingStrip";
54
- import { TypingDots } from "./TypingDots";
55
- import { useCopyAction } from "./useCopyAction";
56
- import { traceToText } from "../store/traceText";
57
- import { useAskBuoySettings } from "../store/useAskBuoySettings";
58
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
59
- function StatusMark({
60
- status
61
- }) {
62
- switch (status) {
63
- case "running":
64
- return /*#__PURE__*/_jsx(ActivityIndicator, {
65
- size: "small",
66
- color: night.color.textSecondary
67
- });
68
- case "ok":
69
- return /*#__PURE__*/_jsx(View, {
70
- style: [styles.mark, styles.markOk],
71
- children: /*#__PURE__*/_jsx(Check, {
72
- size: 10,
73
- color: night.color.onAccent
74
- })
75
- });
76
- case "failed":
77
- return /*#__PURE__*/_jsx(View, {
78
- style: [styles.mark, styles.markFailed],
79
- children: /*#__PURE__*/_jsx(X, {
80
- size: 10,
81
- color: night.color.text
82
- })
83
- });
84
- case "refused":
85
- return /*#__PURE__*/_jsx(View, {
86
- style: [styles.mark, styles.markRefused],
87
- children: /*#__PURE__*/_jsx(AlertTriangle, {
88
- size: 10,
89
- color: night.color.onAccent
90
- })
91
- });
92
- // A grey ring with a bar — drawn, not an icon, so the shape is distinct
93
- // from every outcome the MODEL produced. `declined` had no case at all:
94
- // the row rendered a blank slot, and later nobody could tell whether the
95
- // person or the policy had stopped it.
96
- case "declined":
97
- case "stopped":
98
- return /*#__PURE__*/_jsx(View, {
99
- style: [styles.mark, styles.markNotRun],
100
- children: /*#__PURE__*/_jsx(View, {
101
- style: styles.markNotRunBar
102
- })
103
- });
104
- }
105
- }
106
-
107
- /** The trailing word: what happened, in one glance. */
108
- function statusText(call) {
109
- if (call.status === "running") return {
110
- text: "",
111
- tone: "muted"
112
- };
113
- if (call.status === "refused") return {
114
- text: "Refused",
115
- tone: "warning"
116
- };
117
- if (call.status === "failed") return {
118
- text: "Failed",
119
- tone: "danger"
120
- };
121
- if (call.status === "declined") return {
122
- text: "You declined",
123
- tone: "muted"
124
- };
125
- if (call.status === "stopped") return {
126
- text: "Not run — stopped",
127
- tone: "muted"
128
- };
129
- return {
130
- text: call.summary || "Done",
131
- tone: "muted"
132
- };
133
- }
134
-
135
- /** Rows that never fold: anything that did not simply succeed. */
136
- const isExceptional = c => c.status === "failed" || c.status === "refused" || c.status === "declined" || c.status === "stopped";
137
- function ActivityRow({
138
- call,
139
- receipts,
140
- last
141
- }) {
142
- const [open, setOpen] = useState(false);
143
- const status = statusText(call);
144
- const Chevron = open ? ChevronDown : ChevronRight;
145
- const failureDetail = call.status === "failed" && call.summary && call.summary !== "Failed" ? call.summary : null;
146
- return /*#__PURE__*/_jsxs(View, {
147
- style: [styles.row, !last && styles.rowDivider],
148
- children: [/*#__PURE__*/_jsxs(Pressable, {
149
- testID: `buoy-ask-buoy-tool-${call.toolId}`,
150
- accessibilityRole: "button",
151
- accessibilityLabel: `${call.label}, ${status.text || "running"}`,
152
- accessibilityState: {
153
- expanded: open
154
- },
155
- onPress: () => setOpen(v => !v),
156
- children: [/*#__PURE__*/_jsxs(View, {
157
- style: styles.rowLine,
158
- children: [/*#__PURE__*/_jsx(View, {
159
- style: styles.markSlot,
160
- children: /*#__PURE__*/_jsx(StatusMark, {
161
- status: call.status
162
- })
163
- }), /*#__PURE__*/_jsx(Text, {
164
- style: styles.rowTitle,
165
- children: call.label
166
- }), status.text ? /*#__PURE__*/_jsx(Text, {
167
- style: [styles.rowStatus, status.tone === "danger" && styles.rowStatusDanger, status.tone === "warning" && styles.rowStatusWarning],
168
- numberOfLines: 1,
169
- children: status.text
170
- }) : null, /*#__PURE__*/_jsx(Chevron, {
171
- size: 12,
172
- color: night.color.textTertiary
173
- })]
174
- }), open && /*#__PURE__*/_jsxs(View, {
175
- style: styles.rowDetail,
176
- children: [/*#__PURE__*/_jsx(Text, {
177
- style: styles.rowTool,
178
- children: toolTitle(call.toolId).toUpperCase()
179
- }), call.description ? /*#__PURE__*/_jsx(Text, {
180
- style: styles.rowDescription,
181
- children: call.description
182
- }) : null, failureDetail ? /*#__PURE__*/_jsx(Text, {
183
- style: styles.rowFailure,
184
- children: failureDetail
185
- }) : null]
186
- })]
187
- }), open && receipts.length > 0 && /*#__PURE__*/_jsx(View, {
188
- style: styles.rowReceipts,
189
- testID: `buoy-ask-buoy-tool-receipt-${call.toolId}`,
190
- children: receipts.map(block => /*#__PURE__*/_jsx(BlockRenderer, {
191
- block: block
192
- }, block.id))
193
- })]
194
- });
195
- }
196
-
197
- /**
198
- * Consecutive successful reads fold into one row — "Looked at Network and
199
- * Storage · 3 reads" — expandable to the individual rows. Everything else
200
- * (running, failed, refused, declined, stopped, any write) is always its own
201
- * row, and a group never crosses a write. See store/activityGroups.ts for the
202
- * rule and why the old "Did 7 things" fold was replaced.
203
- */
204
- function ReadsGroup({
205
- label,
206
- count,
207
- calls,
208
- receipts,
209
- last
210
- }) {
211
- const [open, setOpen] = useState(false);
212
- const Chevron = open ? ChevronDown : ChevronRight;
213
- return /*#__PURE__*/_jsxs(View, {
214
- style: !last && !open && styles.rowDivider,
215
- children: [/*#__PURE__*/_jsx(Pressable, {
216
- testID: "buoy-ask-buoy-reads-group",
217
- accessibilityRole: "button",
218
- accessibilityLabel: `${label}, ${count} reads`,
219
- accessibilityState: {
220
- expanded: open
221
- },
222
- onPress: () => setOpen(v => !v),
223
- style: [styles.row, open && styles.rowDivider],
224
- children: /*#__PURE__*/_jsxs(View, {
225
- style: styles.rowLine,
226
- children: [/*#__PURE__*/_jsx(View, {
227
- style: styles.markSlot,
228
- children: /*#__PURE__*/_jsx(StatusMark, {
229
- status: "ok"
230
- })
231
- }), /*#__PURE__*/_jsx(Text, {
232
- style: styles.rowTitle,
233
- children: label
234
- }), /*#__PURE__*/_jsxs(Text, {
235
- style: styles.rowStatus,
236
- numberOfLines: 1,
237
- children: [count, " reads"]
238
- }), /*#__PURE__*/_jsx(Chevron, {
239
- size: 12,
240
- color: night.color.textTertiary
241
- })]
242
- })
243
- }), open && /*#__PURE__*/_jsx(View, {
244
- style: styles.groupBody,
245
- children: calls.map((c, i) => /*#__PURE__*/_jsx(ActivityRow, {
246
- call: c,
247
- receipts: receipts.get(c.id) ?? [],
248
- last: last && i === calls.length - 1
249
- }, c.id))
250
- })]
251
- });
252
- }
253
- function ActivityCard({
254
- calls,
255
- receipts
256
- }) {
257
- const items = groupCalls(calls);
258
- return /*#__PURE__*/_jsx(View, {
259
- style: styles.card,
260
- testID: "buoy-ask-buoy-activity",
261
- children: items.map((item, i) => item.kind === "reads" ? /*#__PURE__*/_jsx(ReadsGroup, {
262
- label: item.label,
263
- count: item.count,
264
- calls: item.calls,
265
- receipts: receipts,
266
- last: i === items.length - 1
267
- }, item.id) : /*#__PURE__*/_jsx(ActivityRow, {
268
- call: item.call,
269
- receipts: receipts.get(item.call.id) ?? [],
270
- last: i === items.length - 1
271
- }, item.call.id))
272
- });
273
- }
274
-
275
- /** Write receipts and model blocks render between the activity card and the prose; Ask blocks last. */
276
- function Blocks({
277
- blocks
278
- }) {
279
- if (blocks.length === 0) return null;
280
- return /*#__PURE__*/_jsx(View, {
281
- style: styles.blocks,
282
- children: blocks.map(({
283
- block
284
- }) => /*#__PURE__*/_jsx(BlockRenderer, {
285
- block: block
286
- }, block.id))
287
- });
288
- }
289
-
290
- /** A line from Buoy, not the model. Dim, centred, no footer, no copy pill. */
291
- function SystemLine({
292
- message
293
- }) {
294
- return /*#__PURE__*/_jsx(View, {
295
- style: styles.systemLine,
296
- testID: "buoy-ask-buoy-system-line",
297
- children: /*#__PURE__*/_jsx(Text, {
298
- style: styles.systemText,
299
- children: message.text
300
- })
301
- });
302
- }
303
- function UserMessage({
304
- message
305
- }) {
306
- return /*#__PURE__*/_jsx(View, {
307
- style: styles.userWrap,
308
- children: /*#__PURE__*/_jsx(View, {
309
- style: styles.userBubble,
310
- children: /*#__PURE__*/_jsx(Text, {
311
- style: styles.userText,
312
- selectable: true,
313
- children: message.text
314
- })
315
- })
316
- });
317
- }
318
- function AssistantMessage({
319
- message,
320
- onRetry,
321
- waitingOnUser
322
- }) {
323
- const {
324
- showThinking
325
- } = useAskBuoySettings();
326
- /**
327
- * The dots run for the WHOLE turn, not just the wait for the first token.
328
- *
329
- * They used to hide as soon as any prose arrived and while a tool call was
330
- * running. A turn that says "pulling the catalogue up — one moment" and
331
- * then works for another minute is the normal shape of this tool, and it
332
- * spent that minute with nothing moving at the bottom of the chat: the
333
- * activity card had already collapsed to "Did 16 things", so the only clue
334
- * anything was still happening was the Stop button and the header
335
- * subtitle. The row spinner in the activity card says which STEP is
336
- * running; this says the TURN is.
337
- *
338
- * The one state it stays out of is a turn waiting on the user — an
339
- * approval or an Ask block. Nothing is loading there.
340
- */
341
- const waiting = !!message.streaming && !waitingOnUser;
342
- const {
343
- nested,
344
- afterText,
345
- ask: askBlocks
346
- } = placeBlocks(message.blocks, message.toolCalls.map(c => c.id));
347
- return /*#__PURE__*/_jsxs(View, {
348
- style: styles.assistantWrap,
349
- children: [message.toolCalls.length > 0 && /*#__PURE__*/_jsx(ActivityCard, {
350
- calls: message.toolCalls,
351
- receipts: nested
352
- }), message.text.length > 0 && /*#__PURE__*/_jsx(Markdown, {
353
- text: message.text,
354
- style: styles.assistantText
355
- }), /*#__PURE__*/_jsx(Blocks, {
356
- blocks: afterText
357
- }), /*#__PURE__*/_jsx(Blocks, {
358
- blocks: askBlocks
359
- }), showThinking && !message.streaming && /*#__PURE__*/_jsx(ThinkingStrip, {
360
- message: message
361
- }), waiting && /*#__PURE__*/_jsx(TypingDots, {}), message.error && /*#__PURE__*/_jsx(ErrorNote, {
362
- error: message.error,
363
- onRetry: onRetry
364
- }), message.stopped && !message.error && /*#__PURE__*/_jsx(StoppedNote, {
365
- onRetry: onRetry,
366
- changed: message.toolCalls.some(c => c.status === "ok" && !!c.effect && c.effect !== "read")
367
- })]
368
- });
369
- }
370
-
371
- /**
372
- * A turn the user stopped.
373
- *
374
- * Deliberately not the error box: they pressed the button. The whole point is
375
- * that it is quiet — a muted line, the same Try-again affordance, and none of
376
- * the red "check your connection" advice that a cancelled fetch used to
377
- * produce. When a write had already landed before the tap, the line says so:
378
- * Stop cannot pull a dispatched change back out of the app, and "Stopped."
379
- * alone read as if nothing had happened.
380
- */
381
- function StoppedNote({
382
- onRetry,
383
- changed
384
- }) {
385
- return /*#__PURE__*/_jsxs(View, {
386
- style: styles.stoppedBox,
387
- testID: "buoy-ask-buoy-stopped",
388
- children: [/*#__PURE__*/_jsx(Text, {
389
- style: styles.stoppedText,
390
- children: changed ? "Stopped. Changes already made are listed above." : "Stopped."
391
- }), onRetry && /*#__PURE__*/_jsx(Pressable, {
392
- testID: "buoy-ask-buoy-retry",
393
- accessibilityRole: "button",
394
- accessibilityLabel: "Try again",
395
- onPress: onRetry,
396
- style: ({
397
- pressed
398
- }) => [styles.retry, pressed && styles.retryPressed],
399
- children: /*#__PURE__*/_jsx(Text, {
400
- style: styles.retryText,
401
- children: "Try again"
402
- })
403
- })]
404
- });
405
- }
406
-
407
- /**
408
- * A failed turn. The headline is for the tester ("couldn't reach the
409
- * endpoint"); the raw message stays underneath for whoever gets the bug
410
- * report, because "fetch failed: UnexpectedException: The network connection
411
- * was lost" is what a developer needs and nothing a tester can act on.
412
- */
413
- function ErrorNote({
414
- error,
415
- onRetry
416
- }) {
417
- const network = /fetch failed|network|ECONN|timed? ?out|socket/i.test(error);
418
- const headline = network ? "Couldn't reach your AI endpoint. Check the connection and try again." : "Something went wrong with that turn.";
419
- return /*#__PURE__*/_jsxs(View, {
420
- style: styles.errorBox,
421
- testID: "buoy-ask-buoy-error",
422
- children: [/*#__PURE__*/_jsx(AlertTriangle, {
423
- size: 14,
424
- color: night.color.danger
425
- }), /*#__PURE__*/_jsxs(View, {
426
- style: styles.errorBody,
427
- children: [/*#__PURE__*/_jsx(Text, {
428
- style: styles.errorText,
429
- children: headline
430
- }), /*#__PURE__*/_jsx(Text, {
431
- style: styles.errorDetail,
432
- selectable: true,
433
- children: error
434
- }), onRetry && /*#__PURE__*/_jsx(Pressable, {
435
- testID: "buoy-ask-buoy-retry",
436
- accessibilityRole: "button",
437
- accessibilityLabel: "Try again",
438
- onPress: onRetry,
439
- style: ({
440
- pressed
441
- }) => [styles.retry, pressed && styles.retryPressed],
442
- children: /*#__PURE__*/_jsx(Text, {
443
- style: styles.retryText,
444
- children: "Try again"
445
- })
446
- })]
447
- })]
448
- });
449
- }
450
-
451
- /**
452
- * The whole conversation as plain text — the only thing Copy ever produces,
453
- * and the only way a session leaves the DEVICE (what it saves to disk for a
454
- * reload goes nowhere — see `transcriptPersistence`).
455
- *
456
- * Plain text on purpose. This lands in a Slack message, a ticket comment or a
457
- * notes file, all of which are read by a person; a JSON document is a worse
458
- * version of that everywhere except a parser, and nothing here parses it.
459
- *
460
- * With `Show agent thinking` on it carries the working too — the reasoning and
461
- * each step with its parameters — because reading the run somewhere that is
462
- * not a phone is the entire point of that setting.
463
- */
464
- export function transcriptToText(messages, withTrace = false) {
465
- const lines = [];
466
- for (const m of messages) {
467
- if (m.role === "user") {
468
- lines.push(`You: ${m.text}`);
469
- continue;
470
- }
471
- if (m.system) {
472
- lines.push(` — ${m.text}`);
473
- continue;
474
- }
475
- if (withTrace) {
476
- lines.push(...traceToText(m));
477
- } else {
478
- for (const c of m.toolCalls) {
479
- const status = isExceptional(c) ? ` (${c.status})` : "";
480
- lines.push(` · ${c.label}${c.summary ? ` — ${c.summary}` : ""}${status}`);
481
- }
482
- }
483
- if (m.text) lines.push(`Ask Buoy: ${m.text}`);
484
- // The cards, too. A finding is the thing a ticket wants and it used to be
485
- // the one thing the paste left out.
486
- for (const t of blocksToText(m.blocks.map(p => p.block))) lines.push(t);
487
- if (m.error) lines.push(` ! ${m.error}`);
488
- if (m.stopped && !m.error) lines.push(" ! Stopped by the user.");
489
- }
490
- return lines.join("\n");
491
- }
492
-
493
- /** Copy, with the result visible for a moment. Shares the header's behaviour. */
494
- function CopyPill({
495
- testID,
496
- label,
497
- getText
498
- }) {
499
- const {
500
- copied,
501
- copy
502
- } = useCopyAction(getText);
503
- return /*#__PURE__*/_jsxs(Pressable, {
504
- testID: testID,
505
- accessibilityRole: "button",
506
- accessibilityLabel: label,
507
- onPress: copy,
508
- style: ({
509
- pressed
510
- }) => [styles.copyBtn, styles.iconBtn, pressed && styles.copyPressed],
511
- children: [copied ? /*#__PURE__*/_jsx(Check, {
512
- size: 11,
513
- color: night.color.accent
514
- }) : /*#__PURE__*/_jsx(Copy, {
515
- size: 11,
516
- color: night.color.textSecondary
517
- }), /*#__PURE__*/_jsx(Text, {
518
- style: [styles.copyText, copied && styles.copiedText],
519
- children: copied ? "Copied" : label
520
- })]
521
- });
522
- }
523
-
524
- /**
525
- * Under a finished answer: the turn's footer and one Copy.
526
- *
527
- * The footer — "48s · 6 actions · 1 declined" — is always on. Duration and
528
- * counts used to exist only inside the opt-in thinking strip, so nothing
529
- * separated one turn from the next and "how long did that take?" had no
530
- * answer without a setting. See `turnFooter` for what is counted and why.
531
- *
532
- * Copy is on EVERY answer rather than only the newest, because the moment you
533
- * decide to keep a conversation is usually a few replies after the one that
534
- * went wrong, and scrolling to the bottom to find the button is friction at
535
- * exactly the wrong time. It copies the WHOLE chat, and now says so: labelled
536
- * "Copy" it read as "copy this answer", and a paste into Slack brought the
537
- * full transcript. The text is built on press from the live store, so this
538
- * component takes no `messages` prop and a streaming answer no longer
539
- * re-renders every finished one.
540
- */
541
- function TranscriptActions({
542
- message
543
- }) {
544
- const {
545
- showThinking
546
- } = useAskBuoySettings();
547
- const getText = useCallback(() => transcriptToText(askBuoyStore.getState().messages, showThinking), [showThinking]);
548
- const footer = turnFooter(message);
549
- return /*#__PURE__*/_jsxs(View, {
550
- style: styles.transcriptActions,
551
- children: [footer ? /*#__PURE__*/_jsx(Text, {
552
- style: styles.turnFooter,
553
- testID: "buoy-ask-buoy-turn-footer",
554
- children: footer
555
- }) : null, /*#__PURE__*/_jsx(CopyPill, {
556
- testID: "buoy-ask-buoy-copy",
557
- label: "Copy conversation",
558
- getText: getText
559
- })]
560
- });
561
- }
562
-
563
- /**
564
- * Memoised: a streaming answer changes the store on every token, and every
565
- * row re-rendered for it. Props are the message itself (a new object when it
566
- * changes), the retry handler (only the newest row gets one) and the waiting
567
- * flag — nothing that changes when a DIFFERENT message streams.
568
- */
569
- const Row = /*#__PURE__*/memo(function Row({
570
- message,
571
- onRetry,
572
- waitingOnUser
573
- }) {
574
- if (message.role === "user") return /*#__PURE__*/_jsx(UserMessage, {
575
- message: message
576
- });
577
- if (message.system) return /*#__PURE__*/_jsx(SystemLine, {
578
- message: message
579
- });
580
- const finished = !message.streaming && !message.error;
581
- return /*#__PURE__*/_jsxs(View, {
582
- children: [/*#__PURE__*/_jsx(AssistantMessage, {
583
- message: message,
584
- onRetry: onRetry,
585
- waitingOnUser: waitingOnUser
586
- }), finished && /*#__PURE__*/_jsx(TranscriptActions, {
587
- message: message
588
- })]
589
- });
590
- });
591
- export function MessageList({
592
- messages,
593
- ListHeaderComponent,
594
- ListEmptyComponent,
595
- ListFooterComponent,
596
- onRetry,
597
- waitingOnUser
598
- }) {
599
- const ref = useRef(null);
600
- // Only auto-scroll when the user is already at the bottom. Yanking them back
601
- // down while they are reading an earlier answer is the classic chat-UI sin.
602
- //
603
- // "At the bottom" is decided from the USER's gestures, not from every scroll
604
- // event. An animated scrollToEnd fires intermediate events while the content
605
- // is still growing under it, and reading those as "the user scrolled up"
606
- // silently unpinned the list mid-answer — the reply then streamed in below
607
- // the fold and the last thing on screen was the question.
608
- const pinned = useRef(true);
609
- const dragging = useRef(false);
610
- const measure = useCallback(e => {
611
- const {
612
- contentOffset,
613
- contentSize,
614
- layoutMeasurement
615
- } = e.nativeEvent;
616
- const distance = contentSize.height - layoutMeasurement.height - contentOffset.y;
617
- pinned.current = distance < 80;
618
- }, []);
619
- const onScrollBeginDrag = useCallback(() => {
620
- dragging.current = true;
621
- }, []);
622
- const onScroll = useCallback(e => {
623
- if (dragging.current) measure(e);
624
- }, [measure]);
625
- const onScrollEndDrag = useCallback(e => {
626
- dragging.current = false;
627
- measure(e);
628
- }, [measure]);
629
- const scrollToEnd = useCallback(() => {
630
- if (!pinned.current) return;
631
- // The size change fires before the new row has finished laying out its
632
- // wrapped text; one frame later the end really is the end.
633
- ref.current?.scrollToEnd({
634
- animated: false
635
- });
636
- requestAnimationFrame(() => ref.current?.scrollToEnd({
637
- animated: false
638
- }));
639
- }, []);
640
-
641
- // A row can grow without the list reporting a size change in the same tick
642
- // (an error box appended to the last message). Follow the transcript too.
643
- const last = messages[messages.length - 1];
644
- const tail = last ? `${last.id}:${last.text.length}:${last.toolCalls.length}:${last.error ?? ""}` : "";
645
- useEffect(() => {
646
- scrollToEnd();
647
- }, [tail, scrollToEnd]);
648
- return /*#__PURE__*/_jsx(FlatList, {
649
- ref: ref,
650
- testID: "buoy-ask-buoy-messages-scroll",
651
- style: styles.list,
652
- contentContainerStyle: styles.listContent,
653
- data: messages,
654
- keyExtractor: m => m.id,
655
- renderItem: ({
656
- item
657
- }) => /*#__PURE__*/_jsx(Row, {
658
- message: item,
659
- onRetry: item.id === last?.id ? onRetry : undefined,
660
- waitingOnUser: item.id === last?.id ? waitingOnUser : false
661
- }),
662
- onScroll: onScroll,
663
- onScrollBeginDrag: onScrollBeginDrag,
664
- onScrollEndDrag: onScrollEndDrag,
665
- onMomentumScrollEnd: measure,
666
- scrollEventThrottle: 32,
667
- onContentSizeChange: scrollToEnd
668
- // Mandatory with a focused input below: without it the first tap after
669
- // typing only dismisses the keyboard and the button never fires.
670
- ,
671
- keyboardShouldPersistTaps: "handled",
672
- keyboardDismissMode: "interactive",
673
- ListHeaderComponent: ListHeaderComponent,
674
- ListEmptyComponent: ListEmptyComponent,
675
- ListFooterComponent: ListFooterComponent
676
- });
677
- }
678
- const styles = StyleSheet.create({
679
- list: {
680
- flex: 1,
681
- backgroundColor: "transparent"
682
- },
683
- listContent: {
684
- paddingHorizontal: night.gutter,
685
- paddingTop: 12,
686
- paddingBottom: 16,
687
- gap: 16,
688
- flexGrow: 1
689
- },
690
- // User
691
- userWrap: {
692
- alignItems: "flex-end"
693
- },
694
- userBubble: {
695
- maxWidth: "84%",
696
- paddingHorizontal: 14,
697
- paddingVertical: 9,
698
- borderRadius: 18,
699
- borderBottomRightRadius: 6,
700
- backgroundColor: night.color.accentFill,
701
- borderWidth: StyleSheet.hairlineWidth,
702
- borderColor: night.color.accentBorder
703
- },
704
- userText: {
705
- color: night.color.text,
706
- fontSize: night.font.row,
707
- lineHeight: 21
708
- },
709
- // Buoy's own lines
710
- systemLine: {
711
- alignSelf: "stretch",
712
- paddingHorizontal: 12,
713
- paddingVertical: 7,
714
- borderRadius: night.radius.row,
715
- backgroundColor: night.color.fill
716
- },
717
- systemText: {
718
- color: night.color.textSecondary,
719
- fontSize: night.font.micro,
720
- lineHeight: 16,
721
- textAlign: "center"
722
- },
723
- // Assistant
724
- assistantWrap: {
725
- alignItems: "stretch",
726
- gap: 10
727
- },
728
- blocks: {
729
- gap: 10
730
- },
731
- assistantText: {
732
- color: night.color.text,
733
- fontSize: night.font.row,
734
- lineHeight: 22,
735
- paddingHorizontal: 2
736
- },
737
- // Activity card
738
- card: {
739
- borderRadius: night.radius.card,
740
- borderWidth: StyleSheet.hairlineWidth,
741
- borderColor: night.color.border,
742
- backgroundColor: night.color.surface,
743
- overflow: "hidden"
744
- },
745
- row: {
746
- paddingHorizontal: 12,
747
- paddingVertical: 10
748
- },
749
- rowDivider: {
750
- borderBottomWidth: StyleSheet.hairlineWidth,
751
- borderBottomColor: night.color.separator
752
- },
753
- rowLine: {
754
- flexDirection: "row",
755
- alignItems: "center",
756
- gap: 10
757
- },
758
- markSlot: {
759
- width: 18,
760
- height: 18,
761
- alignItems: "center",
762
- justifyContent: "center"
763
- },
764
- mark: {
765
- width: 16,
766
- height: 16,
767
- borderRadius: 8,
768
- alignItems: "center",
769
- justifyContent: "center"
770
- },
771
- markOk: {
772
- backgroundColor: night.color.accent
773
- },
774
- markFailed: {
775
- backgroundColor: night.color.danger
776
- },
777
- markRefused: {
778
- backgroundColor: night.color.warning
779
- },
780
- markNotRun: {
781
- borderWidth: 1.5,
782
- borderColor: night.color.textTertiary
783
- },
784
- markNotRunBar: {
785
- width: 7,
786
- height: 1.5,
787
- borderRadius: 1,
788
- backgroundColor: night.color.textSecondary
789
- },
790
- rowTitle: {
791
- flex: 1,
792
- color: night.color.text,
793
- fontSize: night.font.body,
794
- lineHeight: 19
795
- },
796
- rowStatus: {
797
- color: night.color.textTertiary,
798
- fontSize: night.font.caption,
799
- maxWidth: 120
800
- },
801
- rowStatusDanger: {
802
- color: night.color.danger
803
- },
804
- rowStatusWarning: {
805
- color: night.color.warning
806
- },
807
- rowDetail: {
808
- paddingLeft: 28,
809
- paddingTop: 8,
810
- gap: 4
811
- },
812
- rowTool: {
813
- color: night.color.textTertiary,
814
- fontSize: night.font.micro,
815
- fontWeight: "600",
816
- letterSpacing: 0.8
817
- },
818
- rowDescription: {
819
- color: night.color.textSecondary,
820
- fontSize: night.font.caption,
821
- lineHeight: 18
822
- },
823
- rowFailure: {
824
- color: night.color.danger,
825
- fontSize: night.font.caption,
826
- lineHeight: 18
827
- },
828
- rowReceipts: {
829
- paddingTop: 10,
830
- gap: 10
831
- },
832
- groupBody: {
833
- paddingLeft: 14,
834
- backgroundColor: night.color.fill
835
- },
836
- stoppedBox: {
837
- alignItems: "flex-start",
838
- gap: 2,
839
- paddingHorizontal: 12,
840
- paddingVertical: 8
841
- },
842
- stoppedText: {
843
- color: night.color.textTertiary,
844
- fontSize: night.font.caption,
845
- lineHeight: 18
846
- },
847
- // Error
848
- errorBox: {
849
- flexDirection: "row",
850
- alignItems: "flex-start",
851
- gap: 8,
852
- paddingHorizontal: 12,
853
- paddingVertical: 10,
854
- borderRadius: night.radius.row,
855
- backgroundColor: night.color.dangerSoft
856
- },
857
- errorBody: {
858
- flex: 1,
859
- gap: 4
860
- },
861
- errorText: {
862
- color: night.color.danger,
863
- fontSize: night.font.caption,
864
- fontWeight: "600",
865
- lineHeight: 18
866
- },
867
- errorDetail: {
868
- color: night.color.textSecondary,
869
- fontSize: night.font.micro,
870
- lineHeight: 15
871
- },
872
- retry: {
873
- alignSelf: "flex-start",
874
- marginTop: 6,
875
- paddingHorizontal: 14,
876
- paddingVertical: 7,
877
- borderRadius: night.radius.row,
878
- backgroundColor: night.color.button
879
- },
880
- retryPressed: {
881
- opacity: 0.7
882
- },
883
- retryText: {
884
- color: night.color.accent,
885
- fontSize: night.font.caption,
886
- fontWeight: "600"
887
- },
888
- transcriptActions: {
889
- flexDirection: "row",
890
- alignItems: "center",
891
- gap: 10,
892
- marginTop: 8
893
- },
894
- turnFooter: {
895
- flex: 1,
896
- color: night.color.textTertiary,
897
- fontSize: night.font.micro,
898
- fontVariant: ["tabular-nums"]
899
- },
900
- copyBtn: {
901
- paddingHorizontal: 10,
902
- paddingVertical: 5,
903
- borderRadius: night.radius.badge,
904
- backgroundColor: night.color.fill
905
- },
906
- iconBtn: {
907
- flexDirection: "row",
908
- alignItems: "center",
909
- gap: 4,
910
- paddingHorizontal: 8
911
- },
912
- copyPressed: {
913
- opacity: 0.7
914
- },
915
- copyText: {
916
- color: night.color.textSecondary,
917
- fontSize: night.font.micro,
918
- fontWeight: "600"
919
- },
920
- copiedText: {
921
- color: night.color.accent
922
- }
923
- });
924
- //# sourceMappingURL=MessageList.js.map
1
+ "use strict";import{memo,useCallback,useEffect,useRef,useState}from"react";import{ActivityIndicator,FlatList,Pressable,StyleSheet,Text,View}from"react-native";import{AlertTriangle,Check,ChevronDown,ChevronRight,Copy,night,X}from"@buoy-gg/shared-ui";import{toolTitle}from"@buoy-gg/agent-core";import{BlockRenderer}from"../blocks/BlockRenderer";import{blocksToText}from"../blocks/blockText";import{groupCalls,turnFooter}from"../store/activityGroups";import{placeBlocks}from"../store/blockPlacement";import{askBuoyStore}from"../store/askBuoyStore";import{Markdown}from"./Markdown";import{ThinkingStrip}from"./ThinkingStrip";import{TypingDots}from"./TypingDots";import{useCopyAction}from"./useCopyAction";import{traceToText}from"../store/traceText";import{useAskBuoySettings}from"../store/useAskBuoySettings";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";function StatusMark({status:e}){switch(e){case"running":return _jsx(ActivityIndicator,{size:"small",color:night.color.textSecondary});case"ok":return _jsx(View,{style:[styles.mark,styles.markOk],children:_jsx(Check,{size:10,color:night.color.onAccent})});case"failed":return _jsx(View,{style:[styles.mark,styles.markFailed],children:_jsx(X,{size:10,color:night.color.text})});case"refused":return _jsx(View,{style:[styles.mark,styles.markRefused],children:_jsx(AlertTriangle,{size:10,color:night.color.onAccent})});case"declined":case"stopped":return _jsx(View,{style:[styles.mark,styles.markNotRun],children:_jsx(View,{style:styles.markNotRunBar})})}}function statusText(e){return"running"===e.status?{text:"",tone:"muted"}:"refused"===e.status?{text:"Refused",tone:"warning"}:"failed"===e.status?{text:"Failed",tone:"danger"}:"declined"===e.status?{text:"You declined",tone:"muted"}:"stopped"===e.status?{text:"Not run — stopped",tone:"muted"}:"verified"===e.verification?.status?{text:"Verified",tone:"ok"}:"unverified"===e.verification?.status?{text:"Done · not yet visible",tone:"muted"}:"failed"===e.verification?.status?{text:"Done · check failed",tone:"warning"}:{text:e.summary||"Done",tone:"muted"}}const isExceptional=e=>"failed"===e.status||"refused"===e.status||"declined"===e.status||"stopped"===e.status;function ActivityRow({call:e,receipts:t,last:o}){const[s,r]=useState(!1),i=statusText(e),n=s?ChevronDown:ChevronRight,l="failed"===e.status&&e.summary&&"Failed"!==e.summary?e.summary:null;return _jsxs(View,{style:[styles.row,!o&&styles.rowDivider],children:[_jsxs(Pressable,{testID:`buoy-ask-buoy-tool-${e.toolId}`,accessibilityRole:"button",accessibilityLabel:`${e.label}, ${i.text||"running"}`,accessibilityState:{expanded:s},onPress:()=>r(e=>!e),children:[_jsxs(View,{style:styles.rowLine,children:[_jsx(View,{style:styles.markSlot,children:_jsx(StatusMark,{status:e.status})}),_jsx(Text,{style:styles.rowTitle,children:e.label}),i.text?_jsx(Text,{style:[styles.rowStatus,"danger"===i.tone&&styles.rowStatusDanger,"warning"===i.tone&&styles.rowStatusWarning,"ok"===i.tone&&styles.rowStatusOk],numberOfLines:1,children:i.text}):null,_jsx(n,{size:12,color:night.color.textTertiary})]}),s&&_jsxs(View,{style:styles.rowDetail,children:[_jsx(Text,{style:styles.rowTool,children:toolTitle(e.toolId).toUpperCase()}),e.description?_jsx(Text,{style:styles.rowDescription,children:e.description}):null,l?_jsx(Text,{style:styles.rowFailure,children:l}):null,e.verification?_jsx(Text,{style:[styles.rowDescription,"failed"===e.verification.status&&styles.rowFailure,"verified"===e.verification.status&&styles.rowVerified],testID:`buoy-ask-buoy-verification-${e.verification.status}`,children:e.verification.detail}):null]})]}),s&&t.length>0&&_jsx(View,{style:styles.rowReceipts,testID:`buoy-ask-buoy-tool-receipt-${e.toolId}`,children:t.map(e=>_jsx(BlockRenderer,{block:e},e.id))})]})}function ReadsGroup({label:e,count:t,calls:o,receipts:s,last:r}){const[i,n]=useState(!1),l=i?ChevronDown:ChevronRight;return _jsxs(View,{style:!r&&!i&&styles.rowDivider,children:[_jsx(Pressable,{testID:"buoy-ask-buoy-reads-group",accessibilityRole:"button",accessibilityLabel:`${e}, ${t} reads`,accessibilityState:{expanded:i},onPress:()=>n(e=>!e),style:[styles.row,i&&styles.rowDivider],children:_jsxs(View,{style:styles.rowLine,children:[_jsx(View,{style:styles.markSlot,children:_jsx(StatusMark,{status:"ok"})}),_jsx(Text,{style:styles.rowTitle,children:e}),_jsxs(Text,{style:styles.rowStatus,numberOfLines:1,children:[t," reads"]}),_jsx(l,{size:12,color:night.color.textTertiary})]})}),i&&_jsx(View,{style:styles.groupBody,children:o.map((e,t)=>_jsx(ActivityRow,{call:e,receipts:s.get(e.id)??[],last:r&&t===o.length-1},e.id))})]})}function ActivityCard({calls:e,receipts:t}){const o=groupCalls(e);return _jsx(View,{style:styles.card,testID:"buoy-ask-buoy-activity",children:o.map((e,s)=>"reads"===e.kind?_jsx(ReadsGroup,{label:e.label,count:e.count,calls:e.calls,receipts:t,last:s===o.length-1},e.id):_jsx(ActivityRow,{call:e.call,receipts:t.get(e.call.id)??[],last:s===o.length-1},e.call.id))})}function Blocks({blocks:e}){return 0===e.length?null:_jsx(View,{style:styles.blocks,children:e.map(({block:e})=>_jsx(BlockRenderer,{block:e},e.id))})}function SystemLine({message:e}){return _jsx(View,{style:styles.systemLine,testID:"buoy-ask-buoy-system-line",children:_jsx(Text,{style:styles.systemText,children:e.text})})}function UserMessage({message:e}){return _jsx(View,{style:styles.userWrap,children:_jsx(View,{style:styles.userBubble,children:_jsx(Text,{style:styles.userText,selectable:!0,children:e.text})})})}function AssistantMessage({message:e,onRetry:t,waitingOnUser:o}){const{showThinking:s}=useAskBuoySettings(),r=!!e.streaming&&!o,{nested:i,afterText:n,ask:l}=placeBlocks(e.blocks,e.toolCalls.map(e=>e.id));return _jsxs(View,{style:styles.assistantWrap,children:[e.toolCalls.length>0&&_jsx(ActivityCard,{calls:e.toolCalls,receipts:i}),e.text.length>0&&_jsx(Markdown,{text:e.text,style:styles.assistantText}),_jsx(Blocks,{blocks:n}),_jsx(Blocks,{blocks:l}),s&&!e.streaming&&_jsx(ThinkingStrip,{message:e}),r&&_jsxs(View,{style:styles.waitingRow,children:[_jsx(TypingDots,{}),e.waiting?_jsx(Text,{style:styles.waitingText,testID:"buoy-ask-buoy-waiting",children:e.waiting}):null]}),e.error&&_jsx(ErrorNote,{error:e.error,onRetry:t}),e.stopped&&!e.error&&_jsx(StoppedNote,{onRetry:t,changed:e.toolCalls.some(e=>"ok"===e.status&&!!e.effect&&"read"!==e.effect)})]})}function StoppedNote({onRetry:e,changed:t}){return _jsxs(View,{style:styles.stoppedBox,testID:"buoy-ask-buoy-stopped",children:[_jsx(Text,{style:styles.stoppedText,children:t?"Stopped. Changes already made are listed above.":"Stopped."}),e&&_jsx(Pressable,{testID:"buoy-ask-buoy-retry",accessibilityRole:"button",accessibilityLabel:"Try again",onPress:e,style:({pressed:e})=>[styles.retry,e&&styles.retryPressed],children:_jsx(Text,{style:styles.retryText,children:"Try again"})})]})}function ErrorNote({error:e,onRetry:t}){const o=/fetch failed|network|ECONN|timed? ?out|socket/i.test(e)?"Couldn't reach your AI endpoint. Check the connection and try again.":"Something went wrong with that turn.";return _jsxs(View,{style:styles.errorBox,testID:"buoy-ask-buoy-error",children:[_jsx(AlertTriangle,{size:14,color:night.color.danger}),_jsxs(View,{style:styles.errorBody,children:[_jsx(Text,{style:styles.errorText,children:o}),_jsx(Text,{style:styles.errorDetail,selectable:!0,children:e}),t&&_jsx(Pressable,{testID:"buoy-ask-buoy-retry",accessibilityRole:"button",accessibilityLabel:"Try again",onPress:t,style:({pressed:e})=>[styles.retry,e&&styles.retryPressed],children:_jsx(Text,{style:styles.retryText,children:"Try again"})})]})]})}export function transcriptToText(e,t=!1){const o=[];for(const s of e)if("user"!==s.role)if(s.system)o.push(` — ${s.text}`);else{if(t)o.push(...traceToText(s));else for(const e of s.toolCalls){const t=isExceptional(e)?` (${e.status})`:"";o.push(` · ${e.label}${e.summary?` — ${e.summary}`:""}${t}`)}s.text&&o.push(`Ask Buoy: ${s.text}`);for(const e of blocksToText(s.blocks.map(e=>e.block)))o.push(e);s.error&&o.push(` ! ${s.error}`),s.stopped&&!s.error&&o.push(" ! Stopped by the user.")}else o.push(`You: ${s.text}`);return o.join("\n")}function CopyPill({testID:e,label:t,getText:o}){const{copied:s,copy:r}=useCopyAction(o);return _jsxs(Pressable,{testID:e,accessibilityRole:"button",accessibilityLabel:t,onPress:r,style:({pressed:e})=>[styles.copyBtn,styles.iconBtn,e&&styles.copyPressed],children:[s?_jsx(Check,{size:11,color:night.color.accent}):_jsx(Copy,{size:11,color:night.color.textSecondary}),_jsx(Text,{style:[styles.copyText,s&&styles.copiedText],children:s?"Copied":t})]})}function TranscriptActions({message:e}){const{showThinking:t}=useAskBuoySettings(),o=useCallback(()=>transcriptToText(askBuoyStore.getState().messages,t),[t]),s=turnFooter(e);return _jsxs(View,{style:styles.transcriptActions,children:[s?_jsx(Text,{style:styles.turnFooter,testID:"buoy-ask-buoy-turn-footer",children:s}):null,_jsx(CopyPill,{testID:"buoy-ask-buoy-copy",label:"Copy conversation",getText:o})]})}const Row=memo(function({message:e,onRetry:t,waitingOnUser:o}){if("user"===e.role)return _jsx(UserMessage,{message:e});if(e.system)return _jsx(SystemLine,{message:e});const s=!e.streaming&&!e.error;return _jsxs(View,{children:[_jsx(AssistantMessage,{message:e,onRetry:t,waitingOnUser:o}),s&&_jsx(TranscriptActions,{message:e})]})});export function MessageList({messages:e,ListHeaderComponent:t,ListEmptyComponent:o,ListFooterComponent:s,onRetry:r,waitingOnUser:i}){const n=useRef(null),l=useRef(!0),a=useRef(!1),c=useCallback(e=>{const{contentOffset:t,contentSize:o,layoutMeasurement:s}=e.nativeEvent,r=o.height-s.height-t.y;l.current=r<80},[]),d=useCallback(()=>{a.current=!0},[]),y=useCallback(e=>{a.current&&c(e)},[c]),u=useCallback(e=>{a.current=!1,c(e)},[c]),g=useCallback(()=>{l.current&&(n.current?.scrollToEnd({animated:!1}),requestAnimationFrame(()=>n.current?.scrollToEnd({animated:!1})))},[]),h=e[e.length-1],x=h?`${h.id}:${h.text.length}:${h.toolCalls.length}:${h.error??""}`:"";return useEffect(()=>{g()},[x,g]),_jsx(FlatList,{ref:n,testID:"buoy-ask-buoy-messages-scroll",style:styles.list,contentContainerStyle:styles.listContent,data:e,keyExtractor:e=>e.id,renderItem:({item:e})=>_jsx(Row,{message:e,onRetry:e.id===h?.id?r:void 0,waitingOnUser:e.id===h?.id&&i}),onScroll:y,onScrollBeginDrag:d,onScrollEndDrag:u,onMomentumScrollEnd:c,scrollEventThrottle:32,onContentSizeChange:g,keyboardShouldPersistTaps:"handled",keyboardDismissMode:"interactive",ListHeaderComponent:t,ListEmptyComponent:o,ListFooterComponent:s})}const styles=StyleSheet.create({list:{flex:1,backgroundColor:"transparent"},listContent:{paddingHorizontal:night.gutter,paddingTop:12,paddingBottom:16,gap:16,flexGrow:1},userWrap:{alignItems:"flex-end"},userBubble:{maxWidth:"84%",paddingHorizontal:14,paddingVertical:9,borderRadius:18,borderBottomRightRadius:6,backgroundColor:night.color.accentFill,borderWidth:StyleSheet.hairlineWidth,borderColor:night.color.accentBorder},userText:{color:night.color.text,fontSize:night.font.row,lineHeight:21},systemLine:{alignSelf:"stretch",paddingHorizontal:12,paddingVertical:7,borderRadius:night.radius.row,backgroundColor:night.color.fill},systemText:{color:night.color.textSecondary,fontSize:night.font.micro,lineHeight:16,textAlign:"center"},assistantWrap:{alignItems:"stretch",gap:10},blocks:{gap:10},waitingRow:{flexDirection:"row",alignItems:"center",gap:8},waitingText:{color:night.color.textSecondary,fontSize:night.font.caption},assistantText:{color:night.color.text,fontSize:night.font.row,lineHeight:22,paddingHorizontal:2},card:{borderRadius:night.radius.card,borderWidth:StyleSheet.hairlineWidth,borderColor:night.color.border,backgroundColor:night.color.surface,overflow:"hidden"},row:{paddingHorizontal:12,paddingVertical:10},rowDivider:{borderBottomWidth:StyleSheet.hairlineWidth,borderBottomColor:night.color.separator},rowLine:{flexDirection:"row",alignItems:"center",gap:10},markSlot:{width:18,height:18,alignItems:"center",justifyContent:"center"},mark:{width:16,height:16,borderRadius:8,alignItems:"center",justifyContent:"center"},markOk:{backgroundColor:night.color.accent},markFailed:{backgroundColor:night.color.danger},markRefused:{backgroundColor:night.color.warning},markNotRun:{borderWidth:1.5,borderColor:night.color.textTertiary},markNotRunBar:{width:7,height:1.5,borderRadius:1,backgroundColor:night.color.textSecondary},rowTitle:{flex:1,color:night.color.text,fontSize:night.font.body,lineHeight:19},rowStatus:{color:night.color.textTertiary,fontSize:night.font.caption,maxWidth:120},rowStatusDanger:{color:night.color.danger},rowStatusWarning:{color:night.color.warning},rowStatusOk:{color:night.color.accent},rowVerified:{color:night.color.accent},rowDetail:{paddingLeft:28,paddingTop:8,gap:4},rowTool:{color:night.color.textTertiary,fontSize:night.font.micro,fontWeight:"600",letterSpacing:.8},rowDescription:{color:night.color.textSecondary,fontSize:night.font.caption,lineHeight:18},rowFailure:{color:night.color.danger,fontSize:night.font.caption,lineHeight:18},rowReceipts:{paddingTop:10,gap:10},groupBody:{paddingLeft:14,backgroundColor:night.color.fill},stoppedBox:{alignItems:"flex-start",gap:2,paddingHorizontal:12,paddingVertical:8},stoppedText:{color:night.color.textTertiary,fontSize:night.font.caption,lineHeight:18},errorBox:{flexDirection:"row",alignItems:"flex-start",gap:8,paddingHorizontal:12,paddingVertical:10,borderRadius:night.radius.row,backgroundColor:night.color.dangerSoft},errorBody:{flex:1,gap:4},errorText:{color:night.color.danger,fontSize:night.font.caption,fontWeight:"600",lineHeight:18},errorDetail:{color:night.color.textSecondary,fontSize:night.font.micro,lineHeight:15},retry:{alignSelf:"flex-start",marginTop:6,paddingHorizontal:14,paddingVertical:7,borderRadius:night.radius.row,backgroundColor:night.color.button},retryPressed:{opacity:.7},retryText:{color:night.color.accent,fontSize:night.font.caption,fontWeight:"600"},transcriptActions:{flexDirection:"row",alignItems:"center",gap:10,marginTop:8},turnFooter:{flex:1,color:night.color.textTertiary,fontSize:night.font.micro,fontVariant:["tabular-nums"]},copyBtn:{paddingHorizontal:10,paddingVertical:5,borderRadius:night.radius.badge,backgroundColor:night.color.fill},iconBtn:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:8},copyPressed:{opacity:.7},copyText:{color:night.color.textSecondary,fontSize:night.font.micro,fontWeight:"600"},copiedText:{color:night.color.accent}});