@buoy-gg/ask-buoy 7.0.41 → 7.0.43

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