@assistant-ui/react 0.15.16 → 0.15.18

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 (297) hide show
  1. package/README.md +4 -4
  2. package/dist/index.d.ts +8 -4
  3. package/dist/index.js +4 -2
  4. package/dist/mcp-apps/McpAppRenderer.d.ts +36 -9
  5. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  6. package/dist/mcp-apps/McpAppRenderer.js +108 -57
  7. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  8. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  9. package/dist/mcp-apps/McpAppsRemoteHost.js +20 -17
  10. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  11. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  12. package/dist/mcp-apps/app-frame.js +229 -115
  13. package/dist/mcp-apps/app-frame.js.map +1 -1
  14. package/dist/mcp-apps/bridge.js +2 -3
  15. package/dist/mcp-apps/bridge.js.map +1 -1
  16. package/dist/mcp-apps/index.d.ts +3 -3
  17. package/dist/model-context/frame/useAssistantFrameHost.d.ts +1 -1
  18. package/dist/model-context/frame/useAssistantFrameHost.js +10 -11
  19. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  20. package/dist/model-context/makeAssistantVisible.js +1 -1
  21. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  22. package/dist/primitives/actionBar/ActionBarCopy.js +3 -3
  23. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  24. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
  25. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  26. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +3 -3
  27. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  28. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +3 -3
  29. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  30. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  31. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +3 -3
  32. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  33. package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
  34. package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
  35. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
  36. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
  37. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
  38. package/dist/primitives/actionBarMore/scope.d.ts +2 -2
  39. package/dist/primitives/actionBarMore/scope.d.ts.map +1 -1
  40. package/dist/primitives/actionBarMore/scope.js.map +1 -1
  41. package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
  42. package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
  43. package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
  44. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  45. package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
  46. package/dist/primitives/assistantModal/scope.d.ts +2 -2
  47. package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
  48. package/dist/primitives/assistantModal/scope.js.map +1 -1
  49. package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
  50. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  51. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  52. package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
  53. package/dist/primitives/composer/ComposerInput.js +1 -3
  54. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  55. package/dist/primitives/composer/ComposerInputPluginContext.d.ts +1 -2
  56. package/dist/primitives/composer/ComposerInputPluginContext.d.ts.map +1 -1
  57. package/dist/primitives/composer/ComposerInputPluginContext.js +1 -6
  58. package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
  59. package/dist/primitives/composer/ComposerQuote.js +5 -5
  60. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  61. package/dist/primitives/composer/ComposerRoot.js +3 -3
  62. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  63. package/dist/primitives/composer/trigger/TriggerPopover.d.ts +5 -0
  64. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  65. package/dist/primitives/composer/trigger/TriggerPopover.js +157 -130
  66. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  67. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +46 -15
  68. package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
  69. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +3 -3
  70. package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
  71. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +4 -4
  72. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
  73. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +40 -10
  74. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
  75. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +4 -4
  76. package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
  77. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -0
  78. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  79. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +63 -61
  80. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  81. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +2 -0
  82. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
  83. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
  84. package/dist/primitives/composer/trigger/detectTrigger.d.ts +21 -1
  85. package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
  86. package/dist/primitives/composer/trigger/detectTrigger.js +17 -3
  87. package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
  88. package/dist/primitives/composer/trigger/index.d.ts +3 -1
  89. package/dist/primitives/composer/trigger/index.d.ts.map +1 -1
  90. package/dist/primitives/composer/trigger/index.js.map +1 -1
  91. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts +6 -0
  92. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts.map +1 -0
  93. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js +9 -0
  94. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js.map +1 -0
  95. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +3 -4
  96. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  97. package/dist/primitives/composer/trigger/triggerDetectionResource.js +15 -14
  98. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  99. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
  100. package/dist/primitives/composer/trigger/triggerNavigationResource.js +3 -5
  101. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  102. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  103. package/dist/primitives/composer/trigger/triggerSelectionResource.js +18 -19
  104. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  105. package/dist/primitives/composer.d.ts +3 -2
  106. package/dist/primitives/composer.js.map +1 -1
  107. package/dist/primitives/error/ErrorMessage.js.map +1 -1
  108. package/dist/primitives/message/MessageIf.js.map +1 -1
  109. package/dist/primitives/message/MessageParts.js.map +1 -1
  110. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  111. package/dist/primitives/message/MessageRoot.js +4 -4
  112. package/dist/primitives/message/MessageRoot.js.map +1 -1
  113. package/dist/primitives/messagePart/MessagePartText.d.ts +1 -1
  114. package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
  115. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +3 -3
  116. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  117. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +3 -3
  118. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  119. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  120. package/dist/primitives/thread/ThreadViewport.js +3 -3
  121. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  122. package/dist/primitives/thread/ThreadViewportFooter.js +3 -3
  123. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  124. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  125. package/dist/primitives/thread/useThreadViewportAutoScroll.js +4 -4
  126. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  127. package/dist/primitives/threadList/ThreadListNew.js +3 -3
  128. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  129. package/dist/primitives/threadListFocusGroup.d.ts +2 -1
  130. package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
  131. package/dist/primitives/threadListFocusGroup.js +2 -2
  132. package/dist/primitives/threadListFocusGroup.js.map +1 -1
  133. package/dist/primitives/threadListItem/ThreadListItemRoot.js +3 -3
  134. package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
  135. package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -1
  136. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  137. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
  138. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
  139. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
  140. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -1
  141. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
  142. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
  143. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
  144. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
  145. package/dist/primitives/threadListItemMore/scope.d.ts +2 -2
  146. package/dist/primitives/threadListItemMore/scope.d.ts.map +1 -1
  147. package/dist/primitives/threadListItemMore/scope.js.map +1 -1
  148. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  149. package/dist/sandbox-host/SandboxHost.js +148 -72
  150. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  151. package/dist/unstable/useLiveCompletionAdapter.js +3 -1
  152. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  153. package/dist/unstable/useMentionAdapter.d.ts +9 -3
  154. package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
  155. package/dist/unstable/useMentionAdapter.js +50 -43
  156. package/dist/unstable/useMentionAdapter.js.map +1 -1
  157. package/dist/unstable/useModelContextSnapshot.d.ts +34 -0
  158. package/dist/unstable/useModelContextSnapshot.d.ts.map +1 -0
  159. package/dist/unstable/useModelContextSnapshot.js +61 -0
  160. package/dist/unstable/useModelContextSnapshot.js.map +1 -0
  161. package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
  162. package/dist/unstable/useSlashCommandAdapter.js +37 -29
  163. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  164. package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts +14 -0
  165. package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts.map +1 -0
  166. package/dist/unstable/webmcp/WebMcpRegistrationResource.js +85 -0
  167. package/dist/unstable/webmcp/WebMcpRegistrationResource.js.map +1 -0
  168. package/dist/unstable/webmcp/convertTools.d.ts +21 -0
  169. package/dist/unstable/webmcp/convertTools.d.ts.map +1 -0
  170. package/dist/unstable/webmcp/convertTools.js +110 -0
  171. package/dist/unstable/webmcp/convertTools.js.map +1 -0
  172. package/dist/unstable/webmcp/useWebMcpProvider.d.ts +21 -0
  173. package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -0
  174. package/dist/unstable/webmcp/useWebMcpProvider.js +105 -0
  175. package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -0
  176. package/dist/unstable/webmcp/webmcp-host.d.ts +39 -0
  177. package/dist/unstable/webmcp/webmcp-host.d.ts.map +1 -0
  178. package/dist/unstable/webmcp/webmcp-host.js +29 -0
  179. package/dist/unstable/webmcp/webmcp-host.js.map +1 -0
  180. package/dist/utils/Primitive.d.ts +6 -6
  181. package/dist/utils/Primitive.js +2 -2
  182. package/dist/utils/Primitive.js.map +1 -1
  183. package/dist/utils/createActionButton.js +3 -3
  184. package/dist/utils/createActionButton.js.map +1 -1
  185. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  186. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  187. package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
  188. package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
  189. package/dist/utils/smooth/SmoothContext.d.ts +24 -0
  190. package/dist/utils/smooth/SmoothContext.d.ts.map +1 -1
  191. package/dist/utils/smooth/useSmooth.js +1 -1
  192. package/dist/utils/smooth/useSmooth.js.map +1 -1
  193. package/package.json +14 -22
  194. package/src/index.ts +22 -1
  195. package/src/mcp-apps/McpAppRenderer.test.tsx +386 -9
  196. package/src/mcp-apps/McpAppRenderer.tsx +171 -69
  197. package/src/mcp-apps/McpAppsRemoteHost.concurrent.test.tsx +215 -0
  198. package/src/mcp-apps/McpAppsRemoteHost.ts +28 -12
  199. package/src/mcp-apps/app-frame.test.tsx +349 -0
  200. package/src/mcp-apps/app-frame.tsx +68 -9
  201. package/src/mcp-apps/bridge.ts +7 -2
  202. package/src/mcp-apps/index.ts +6 -1
  203. package/src/model-context/frame/AssistantFrame.test.ts +3 -3
  204. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
  205. package/src/model-context/frame/useAssistantFrameHost.ts +2 -2
  206. package/src/model-context/makeAssistantVisible.tsx +1 -1
  207. package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
  208. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  209. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
  210. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
  211. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +1 -1
  212. package/src/primitives/actionBarMore/scope.ts +2 -2
  213. package/src/primitives/assistantModal/AssistantModalContent.tsx +1 -1
  214. package/src/primitives/assistantModal/scope.ts +2 -2
  215. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
  216. package/src/primitives/composer/ComposerInput.test.tsx +2 -1
  217. package/src/primitives/composer/ComposerInput.tsx +3 -3
  218. package/src/primitives/composer/ComposerInputPluginContext.tsx +0 -10
  219. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  220. package/src/primitives/composer/ComposerRoot.tsx +1 -1
  221. package/src/primitives/composer/trigger/TriggerPopover.tsx +16 -1
  222. package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +4 -2
  223. package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +1 -1
  224. package/src/primitives/composer/trigger/TriggerPopoverBehavior.test.tsx +109 -0
  225. package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +1 -1
  226. package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +4 -2
  227. package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +1 -1
  228. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +4 -1
  229. package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +2 -0
  230. package/src/primitives/composer/trigger/detectTrigger.test.ts +88 -5
  231. package/src/primitives/composer/trigger/detectTrigger.ts +50 -9
  232. package/src/primitives/composer/trigger/index.ts +1 -0
  233. package/src/primitives/composer/trigger/matchesTriggerItemQuery.test.ts +52 -0
  234. package/src/primitives/composer/trigger/matchesTriggerItemQuery.ts +13 -0
  235. package/src/primitives/composer/trigger/triggerDetectionResource.ts +10 -7
  236. package/src/primitives/composer/trigger/triggerNavigationResource.ts +3 -10
  237. package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +15 -3
  238. package/src/primitives/composer/trigger/triggerSelectionResource.ts +1 -4
  239. package/src/primitives/composer.ts +1 -0
  240. package/src/primitives/message/MessageRoot.tsx +1 -1
  241. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  242. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +1 -1
  243. package/src/primitives/thread/ThreadViewport.tsx +1 -1
  244. package/src/primitives/thread/ThreadViewportFooter.tsx +1 -1
  245. package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -7
  246. package/src/primitives/threadList/ThreadListNew.tsx +1 -1
  247. package/src/primitives/threadListFocusGroup.ts +2 -2
  248. package/src/primitives/threadListItem/ThreadListItemRoot.tsx +1 -1
  249. package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +1 -1
  250. package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +1 -1
  251. package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +1 -1
  252. package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +2 -2
  253. package/src/primitives/threadListItemMore/scope.ts +2 -2
  254. package/src/sandbox-host/SandboxHost.test.tsx +165 -3
  255. package/src/sandbox-host/SandboxHost.tsx +42 -9
  256. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +31 -1
  257. package/src/unstable/useComposerInputHistory.test.tsx +2 -1
  258. package/src/unstable/useLiveCompletionAdapter.test.tsx +60 -1
  259. package/src/unstable/useLiveCompletionAdapter.ts +4 -1
  260. package/src/unstable/useMentionAdapter.test.tsx +337 -0
  261. package/src/unstable/useMentionAdapter.ts +94 -57
  262. package/src/unstable/useModelContextSnapshot.test.tsx +132 -0
  263. package/src/unstable/useModelContextSnapshot.ts +67 -0
  264. package/src/unstable/useSlashCommandAdapter.test.tsx +141 -0
  265. package/src/unstable/useSlashCommandAdapter.ts +47 -19
  266. package/src/unstable/webmcp/WebMcpRegistrationResource.ts +92 -0
  267. package/src/unstable/webmcp/__tests__/webmcp.fake.tsx +168 -0
  268. package/src/unstable/webmcp/convertTools.test.ts +393 -0
  269. package/src/unstable/webmcp/convertTools.ts +180 -0
  270. package/src/unstable/webmcp/useWebMcpProvider.test.tsx +466 -0
  271. package/src/unstable/webmcp/useWebMcpProvider.ts +150 -0
  272. package/src/unstable/webmcp/webmcp-host.test.ts +142 -0
  273. package/src/unstable/webmcp/webmcp-host.ts +83 -0
  274. package/src/utils/Primitive.tsx +2 -2
  275. package/src/utils/createActionButton.tsx +1 -1
  276. package/src/utils/hooks/useOnResizeContent.ts +1 -1
  277. package/src/utils/hooks/useOnScrollToBottom.ts +1 -1
  278. package/src/utils/smooth/useSmooth.ts +1 -1
  279. package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +0 -3
  280. package/dist/legacy-runtime/runtime/MessagePartRuntime.js +0 -2
  281. package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +0 -3
  282. package/dist/legacy-runtime/runtime/MessageRuntime.js +0 -2
  283. package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +0 -3
  284. package/dist/legacy-runtime/runtime/ThreadListRuntime.js +0 -2
  285. package/dist/utils/invokeCallbackSafely.d.ts +0 -5
  286. package/dist/utils/invokeCallbackSafely.d.ts.map +0 -1
  287. package/dist/utils/invokeCallbackSafely.js +0 -15
  288. package/dist/utils/invokeCallbackSafely.js.map +0 -1
  289. package/dist/utils/useToolArgsFieldStatus.d.ts +0 -15
  290. package/dist/utils/useToolArgsFieldStatus.d.ts.map +0 -1
  291. package/dist/utils/useToolArgsFieldStatus.js +0 -23
  292. package/dist/utils/useToolArgsFieldStatus.js.map +0 -1
  293. package/src/legacy-runtime/runtime/MessagePartRuntime.ts +0 -2
  294. package/src/legacy-runtime/runtime/MessageRuntime.ts +0 -3
  295. package/src/legacy-runtime/runtime/ThreadListRuntime.ts +0 -4
  296. package/src/utils/invokeCallbackSafely.ts +0 -11
  297. package/src/utils/useToolArgsFieldStatus.ts +0 -18
@@ -0,0 +1,13 @@
1
+ import type { Unstable_TriggerItem } from "@assistant-ui/core";
2
+
3
+ export function matchesTriggerItemQuery(
4
+ item: Unstable_TriggerItem,
5
+ lowerQuery: string,
6
+ ): boolean {
7
+ if (!lowerQuery) return true;
8
+ return (
9
+ item.id.toLowerCase().includes(lowerQuery) ||
10
+ item.label.toLowerCase().includes(lowerQuery) ||
11
+ (item.description?.toLowerCase().includes(lowerQuery) ?? false)
12
+ );
13
+ }
@@ -1,12 +1,13 @@
1
1
  import { useMemo, useState } from "react";
2
2
  import { resource } from "@assistant-ui/tap";
3
- import { detectTrigger } from "./detectTrigger";
3
+ import {
4
+ detectTrigger,
5
+ type TriggerMatch,
6
+ type TriggerMatcher,
7
+ } from "./detectTrigger";
4
8
 
5
9
  /** Detected trigger position within the composer text. */
6
- export type DetectedTrigger = {
7
- readonly offset: number;
8
- readonly query: string;
9
- };
10
+ export type DetectedTrigger = TriggerMatch;
10
11
 
11
12
  export type TriggerDetectionResourceOutput = {
12
13
  /** Detected trigger (or `null` when inactive). */
@@ -21,16 +22,18 @@ export type TriggerDetectionResourceOutput = {
21
22
  const useTriggerDetectionResource = ({
22
23
  text,
23
24
  triggerChar,
25
+ matcher,
24
26
  }: {
25
27
  text: string;
26
28
  triggerChar: string;
29
+ matcher?: TriggerMatcher | undefined;
27
30
  }): TriggerDetectionResourceOutput => {
28
31
  const [cursorPosition, setCursorPosition] = useState(text.length);
29
32
 
30
33
  const trigger = useMemo(() => {
31
34
  const pos = Math.min(cursorPosition, text.length);
32
- return detectTrigger(text, triggerChar, pos);
33
- }, [cursorPosition, text, triggerChar]);
35
+ return detectTrigger(text, triggerChar, pos, matcher);
36
+ }, [cursorPosition, matcher, text, triggerChar]);
34
37
 
35
38
  const query = trigger?.query ?? "";
36
39
 
@@ -5,14 +5,7 @@ import type {
5
5
  Unstable_TriggerCategory,
6
6
  Unstable_TriggerItem,
7
7
  } from "@assistant-ui/core";
8
-
9
- function matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {
10
- return (
11
- item.id.toLowerCase().includes(lower) ||
12
- item.label.toLowerCase().includes(lower) ||
13
- (item.description?.toLowerCase().includes(lower) ?? false)
14
- );
15
- }
8
+ import { matchesTriggerItemQuery } from "./matchesTriggerItemQuery";
16
9
 
17
10
  export type TriggerNavigationResourceOutput = {
18
11
  /** Filtered categories visible in the list (empty in search mode). */
@@ -76,7 +69,7 @@ const useTriggerNavigationResource = ({
76
69
  const lower = query.toLowerCase();
77
70
  for (const cat of categories) {
78
71
  for (const item of adapter.categoryItems(cat.id)) {
79
- if (matchesQuery(item, lower)) {
72
+ if (matchesTriggerItemQuery(item, lower)) {
80
73
  all.push(item);
81
74
  }
82
75
  }
@@ -97,7 +90,7 @@ const useTriggerNavigationResource = ({
97
90
  if (isSearchMode) return searchResults ?? [];
98
91
  if (!query) return allItems;
99
92
  const lower = query.toLowerCase();
100
- return allItems.filter((item) => matchesQuery(item, lower));
93
+ return allItems.filter((item) => matchesTriggerItemQuery(item, lower));
101
94
  }, [allItems, query, isSearchMode, searchResults]);
102
95
 
103
96
  const navigableList = useMemo(() => {
@@ -45,7 +45,7 @@ const render = ({
45
45
  }: {
46
46
  behavior: TriggerBehavior;
47
47
  text: string;
48
- trigger: { offset: number; query: string };
48
+ trigger: { offset: number; query: string; endOffset: number };
49
49
  }) => {
50
50
  const textRef = { value: text };
51
51
  const { aui, setText } = makeAui(textRef);
@@ -72,7 +72,7 @@ describe("TriggerSelectionResource", () => {
72
72
  const { root, setText, setCursorPosition, onSelected } = render({
73
73
  behavior: { kind: "directive", formatter },
74
74
  text: "@file tail",
75
- trigger: { offset: 0, query: "file" },
75
+ trigger: { offset: 0, query: "file", endOffset: 5 },
76
76
  });
77
77
 
78
78
  root.getValue().selectItem(item);
@@ -91,7 +91,7 @@ describe("TriggerSelectionResource", () => {
91
91
  removeOnExecute: true,
92
92
  },
93
93
  text: "before @file tail",
94
- trigger: { offset: 7, query: "file" },
94
+ trigger: { offset: 7, query: "file", endOffset: 12 },
95
95
  });
96
96
 
97
97
  root.getValue().selectItem(item);
@@ -132,4 +132,16 @@ describe("TriggerSelectionResource", () => {
132
132
  expect(setText).toHaveBeenCalledWith("@file.txt ");
133
133
  expect(textRef.value).toBe("@file.txt ");
134
134
  });
135
+
136
+ it("replaces the matcher endOffset even when query is shorter", () => {
137
+ const { root, setText } = render({
138
+ behavior: { kind: "directive", formatter },
139
+ text: "hello @Example UK tail",
140
+ trigger: { offset: 6, query: "example", endOffset: 17 },
141
+ });
142
+
143
+ root.getValue().selectItem(item);
144
+
145
+ expect(setText).toHaveBeenCalledWith("hello @file.txt tail");
146
+ });
135
147
  });
@@ -37,7 +37,6 @@ const useTriggerSelectionResource = ({
37
37
  behavior,
38
38
  trigger,
39
39
  aui,
40
- triggerChar,
41
40
  setCursorPosition,
42
41
  onSelected,
43
42
  }: {
@@ -74,9 +73,7 @@ const useTriggerSelectionResource = ({
74
73
 
75
74
  const currentText = aui.composer.getState().text;
76
75
  const before = currentText.slice(0, trigger.offset);
77
- const after = currentText.slice(
78
- trigger.offset + triggerChar.length + trigger.query.length,
79
- );
76
+ const after = currentText.slice(trigger.endOffset);
80
77
 
81
78
  const insertDirective = () => {
82
79
  const directive = behavior.formatter.serialize(item);
@@ -29,3 +29,4 @@ export { useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScop
29
29
  export { useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers } from "./composer/trigger";
30
30
  export { useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional } from "./composer/trigger";
31
31
  export type { RegisteredTrigger as Unstable_RegisteredTrigger } from "./composer/trigger";
32
+ export type { TriggerMatcher as Unstable_TriggerMatcher } from "./composer/trigger";
@@ -10,7 +10,7 @@ import {
10
10
  } from "react";
11
11
  import { useAui, useAuiState } from "@assistant-ui/store";
12
12
  import { useManagedRef } from "../../utils/hooks/useManagedRef";
13
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
13
+ import { useComposedRefs } from "radix-ui/internal";
14
14
  import {
15
15
  useThreadViewport,
16
16
  useThreadViewportStore,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { Primitive } from "../../utils/Primitive";
4
- import { composeEventHandlers } from "@radix-ui/primitive";
4
+ import { composeEventHandlers } from "radix-ui/internal";
5
5
  import {
6
6
  type ComponentPropsWithoutRef,
7
7
  type ComponentRef,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { Primitive } from "../../utils/Primitive";
4
- import { composeEventHandlers } from "@radix-ui/primitive";
4
+ import { composeEventHandlers } from "radix-ui/internal";
5
5
  import {
6
6
  type ComponentPropsWithoutRef,
7
7
  type ComponentRef,
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
3
+ import { useComposedRefs } from "radix-ui/internal";
4
4
  import { Primitive } from "../../utils/Primitive";
5
5
  import {
6
6
  type ComponentRef,
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
3
+ import { useComposedRefs } from "radix-ui/internal";
4
4
  import { Primitive } from "../../utils/Primitive";
5
5
  import {
6
6
  type ComponentRef,
@@ -1,9 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
3
+ import { useComposedRefs } from "radix-ui/internal";
4
4
  import { useCallback, useLayoutEffect, useRef, type RefCallback } from "react";
5
5
  import { useAuiEvent, useAuiState } from "@assistant-ui/store";
6
- import { isUserScrollUp } from "@assistant-ui/store/client";
6
+ import {
7
+ isUserScrollUp,
8
+ isViewportAtBottom,
9
+ viewportOverflows,
10
+ } from "@assistant-ui/store/client";
7
11
  import { useOnResizeContent } from "../../utils/hooks/useOnResizeContent";
8
12
  import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
9
13
  import { useManagedRef } from "../../utils/hooks/useManagedRef";
@@ -114,9 +118,7 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
114
118
  if (!div) return;
115
119
 
116
120
  const isAtBottom = threadViewportStore.getState().isAtBottom;
117
- const newIsAtBottom =
118
- Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) <= 1 ||
119
- div.scrollHeight <= div.clientHeight;
121
+ const newIsAtBottom = isViewportAtBottom(div);
120
122
 
121
123
  const isInFlightDownwardScroll =
122
124
  !newIsAtBottom && lastScrollTop.current < div.scrollTop;
@@ -135,8 +137,7 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
135
137
  if (newIsAtBottom) {
136
138
  // newIsAtBottom is ambiguous when the viewport doesn't overflow —
137
139
  // keep intent alive until content can actually scroll
138
- const viewportOverflows = div.scrollHeight > div.clientHeight + 1;
139
- if (viewportOverflows) {
140
+ if (viewportOverflows(div)) {
140
141
  scrollingToBottomBehaviorRef.current = null;
141
142
  }
142
143
  if (autoScroll) followBottomRef.current = true;
@@ -6,7 +6,7 @@ import type {
6
6
  } from "../../utils/createActionButton";
7
7
  import { forwardRef } from "react";
8
8
  import { Primitive } from "../../utils/Primitive";
9
- import { composeEventHandlers } from "@radix-ui/primitive";
9
+ import { composeEventHandlers } from "radix-ui/internal";
10
10
  import { useAuiState } from "@assistant-ui/store";
11
11
  import { useThreadListNew as useThreadListNewBehavior } from "@assistant-ui/core/react";
12
12
 
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { createCollection } from "@radix-ui/react-collection";
3
+ import { Collection } from "radix-ui/internal";
4
4
  import { createContext, useContext, type RefObject } from "react";
5
5
 
6
6
  const [collection, useThreadListCollection] =
7
- createCollection<HTMLButtonElement>("ThreadList");
7
+ Collection.createCollection<HTMLButtonElement>("ThreadList");
8
8
 
9
9
  export const ThreadListCollection = collection;
10
10
  export { useThreadListCollection };
@@ -10,7 +10,7 @@ import {
10
10
  useRef,
11
11
  } from "react";
12
12
  import { useAuiState } from "@assistant-ui/store";
13
- import { composeEventHandlers } from "@radix-ui/primitive";
13
+ import { composeEventHandlers } from "radix-ui/internal";
14
14
  import { Direction } from "radix-ui";
15
15
  import {
16
16
  ThreadListItemFocusProvider,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { forwardRef } from "react";
4
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
4
+ import { useComposedRefs } from "radix-ui/internal";
5
5
  import {
6
6
  type ActionButtonElement,
7
7
  type ActionButtonProps,
@@ -6,7 +6,7 @@ import {
6
6
  forwardRef,
7
7
  } from "react";
8
8
  import { Direction, DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
9
- import { composeEventHandlers } from "@radix-ui/primitive";
9
+ import { composeEventHandlers } from "radix-ui/internal";
10
10
  import type { WithRenderPropProps } from "../../utils/Primitive";
11
11
  import { DropdownMenuRenderContent } from "../dropdownMenuRenderPrimitives";
12
12
  import { useThreadListItemFocus } from "../threadListFocusGroup";
@@ -8,7 +8,7 @@ import {
8
8
  useContext,
9
9
  } from "react";
10
10
  import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
11
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
11
+ import { useControllableState } from "radix-ui/internal";
12
12
  import { type ScopedProps, useDropdownMenuScope } from "./scope";
13
13
 
14
14
  const ThreadListItemMoreSetOpenContext = createContext<
@@ -5,8 +5,8 @@ import {
5
5
  Direction,
6
6
  type DropdownMenu as DropdownMenuPrimitive,
7
7
  } from "radix-ui";
8
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
9
- import { composeEventHandlers } from "@radix-ui/primitive";
8
+ import { useComposedRefs } from "radix-ui/internal";
9
+ import { composeEventHandlers } from "radix-ui/internal";
10
10
  import type { WithRenderPropProps } from "../../utils/Primitive";
11
11
  import { DropdownMenuRenderTrigger } from "../dropdownMenuRenderPrimitives";
12
12
  import { useThreadListItemFocus } from "../threadListFocusGroup";
@@ -1,8 +1,8 @@
1
1
  import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
2
- import type { Scope } from "@radix-ui/react-context";
2
+ import type { Context } from "radix-ui/internal";
3
3
 
4
4
  export const useDropdownMenuScope: ReturnType<
5
5
  typeof DropdownMenuPrimitive.createDropdownMenuScope
6
6
  > = DropdownMenuPrimitive.createDropdownMenuScope();
7
7
 
8
- export type ScopedProps<P> = P & { __scopeThreadListItemMore?: Scope };
8
+ export type ScopedProps<P> = P & { __scopeThreadListItemMore?: Context.Scope };
@@ -1,11 +1,12 @@
1
1
  // @vitest-environment jsdom
2
- import { act } from "react";
2
+ import { act, startTransition, Suspense } from "react";
3
3
  import { createRoot, type Root } from "react-dom/client";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
 
6
6
  const { renderHtmlMock } = vi.hoisted(() => ({ renderHtmlMock: vi.fn() }));
7
7
 
8
- vi.mock("safe-content-frame", () => ({
8
+ vi.mock("safe-content-frame", async (importOriginal) => ({
9
+ ...(await importOriginal<typeof import("safe-content-frame")>()),
9
10
  SafeContentFrame: class {
10
11
  renderHtml = renderHtmlMock;
11
12
  },
@@ -34,7 +35,7 @@ function fakeRendered() {
34
35
  origin: "https://fake.scf.test",
35
36
  sendMessage: vi.fn(),
36
37
  dispose: vi.fn(),
37
- fullyLoadedPromiseWithTimeout: vi.fn(),
38
+ fullyLoadedPromiseWithTimeout: vi.fn(() => new Promise<void>(() => {})),
38
39
  };
39
40
  }
40
41
 
@@ -140,6 +141,123 @@ describe("SandboxHost", () => {
140
141
  expect(onMessage).toHaveBeenCalledTimes(1);
141
142
  });
142
143
 
144
+ it("reports a frame that never finishes loading through onError", async () => {
145
+ const rendered = fakeRendered();
146
+ rendered.fullyLoadedPromiseWithTimeout.mockImplementation(() =>
147
+ Promise.reject(
148
+ Object.assign(new Error("Failed to load shim: https://fake.scf.test"), {
149
+ code: "shim-unavailable",
150
+ }),
151
+ ),
152
+ );
153
+ renderHtmlMock.mockResolvedValue(rendered);
154
+ const onError = vi.fn();
155
+
156
+ await act(async () => {
157
+ root.render(
158
+ <SandboxHost
159
+ content={{ html: "" }}
160
+ contentKey="k"
161
+ createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
162
+ onError={onError}
163
+ />,
164
+ );
165
+ });
166
+ await flush();
167
+
168
+ expect(onError).toHaveBeenCalledTimes(1);
169
+ expect(onError.mock.calls[0]![0]).toBeInstanceOf(Error);
170
+ expect(onError.mock.calls[0]![0]).toMatchObject({
171
+ code: "shim-unavailable",
172
+ message: "Failed to load shim: https://fake.scf.test",
173
+ });
174
+ expect(rendered.dispose).not.toHaveBeenCalled();
175
+ });
176
+
177
+ it("stays silent when the shim started and the render is merely slow", async () => {
178
+ const rendered = fakeRendered();
179
+ rendered.fullyLoadedPromiseWithTimeout.mockImplementation(() =>
180
+ Promise.reject(
181
+ Object.assign(new Error("Timeout"), { code: "render-timeout" }),
182
+ ),
183
+ );
184
+ renderHtmlMock.mockResolvedValue(rendered);
185
+ const onError = vi.fn();
186
+
187
+ await act(async () => {
188
+ root.render(
189
+ <SandboxHost
190
+ content={{ html: "" }}
191
+ contentKey="k"
192
+ createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
193
+ onError={onError}
194
+ />,
195
+ );
196
+ });
197
+ await flush();
198
+
199
+ expect(rendered.fullyLoadedPromiseWithTimeout).toHaveBeenCalled();
200
+ expect(onError).not.toHaveBeenCalled();
201
+ expect(rendered.dispose).not.toHaveBeenCalled();
202
+ });
203
+
204
+ it("reports a load failure that carries no shim code", async () => {
205
+ const rendered = fakeRendered();
206
+ rendered.fullyLoadedPromiseWithTimeout.mockImplementation(() =>
207
+ Promise.reject(new Error("Failed to load iframe")),
208
+ );
209
+ renderHtmlMock.mockResolvedValue(rendered);
210
+ const onError = vi.fn();
211
+
212
+ await act(async () => {
213
+ root.render(
214
+ <SandboxHost
215
+ content={{ html: "" }}
216
+ contentKey="k"
217
+ createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
218
+ onError={onError}
219
+ />,
220
+ );
221
+ });
222
+ await flush();
223
+
224
+ expect(onError).toHaveBeenCalledTimes(1);
225
+ expect(onError.mock.calls[0]![0].message).toBe("Failed to load iframe");
226
+ });
227
+
228
+ it("does not report a load failure after unmount", async () => {
229
+ const rendered = fakeRendered();
230
+ let rejectLoad: (error: Error) => void;
231
+ rendered.fullyLoadedPromiseWithTimeout.mockReturnValue(
232
+ new Promise<void>((_, reject) => {
233
+ rejectLoad = reject;
234
+ }),
235
+ );
236
+ renderHtmlMock.mockResolvedValue(rendered);
237
+ const onError = vi.fn();
238
+
239
+ await act(async () => {
240
+ root.render(
241
+ <SandboxHost
242
+ content={{ html: "" }}
243
+ contentKey="k"
244
+ createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
245
+ onError={onError}
246
+ />,
247
+ );
248
+ });
249
+ await flush();
250
+
251
+ await act(async () => {
252
+ root.unmount();
253
+ });
254
+ rejectLoad!(new Error("Timeout"));
255
+ await flush();
256
+
257
+ expect(rendered.fullyLoadedPromiseWithTimeout).toHaveBeenCalled();
258
+ expect(onError).not.toHaveBeenCalled();
259
+ });
260
+
143
261
  it("clamps the bridge-reported height to maxHeight and ignores invalid values", async () => {
144
262
  const rendered = fakeRendered();
145
263
  renderHtmlMock.mockResolvedValue(rendered);
@@ -318,4 +436,48 @@ describe("SandboxHost", () => {
318
436
  });
319
437
  expect(rendered.dispose).toHaveBeenCalledTimes(1);
320
438
  });
439
+
440
+ it("keeps bridge options scoped to committed renders", async () => {
441
+ let resolveRender!: (frame: ReturnType<typeof fakeRendered>) => void;
442
+ renderHtmlMock.mockReturnValue(
443
+ new Promise((resolve) => {
444
+ resolveRender = resolve;
445
+ }),
446
+ );
447
+ const rendered = fakeRendered();
448
+ const bridge = { onMessage: vi.fn(), dispose: vi.fn() };
449
+ const createBridgeA = vi.fn(() => bridge);
450
+ const createBridgeB = vi.fn(() => bridge);
451
+ const interruptedRender = vi.fn();
452
+ const pending = new Promise<never>(() => {});
453
+ const Block = () => {
454
+ interruptedRender();
455
+ throw pending;
456
+ };
457
+ const view = (createBridge: typeof createBridgeA, blocked: boolean) => (
458
+ <Suspense fallback={null}>
459
+ <SandboxHost
460
+ content={{ html: "" }}
461
+ contentKey="k"
462
+ createBridge={createBridge}
463
+ />
464
+ {blocked ? <Block /> : null}
465
+ </Suspense>
466
+ );
467
+
468
+ await act(async () => {
469
+ root.render(view(createBridgeA, false));
470
+ });
471
+ act(() => {
472
+ startTransition(() => root.render(view(createBridgeB, true)));
473
+ });
474
+ await vi.waitFor(() => expect(interruptedRender).toHaveBeenCalled());
475
+ await act(async () => {
476
+ resolveRender(rendered);
477
+ await new Promise((resolve) => setTimeout(resolve, 0));
478
+ });
479
+
480
+ expect(createBridgeA).toHaveBeenCalledTimes(1);
481
+ expect(createBridgeB).not.toHaveBeenCalled();
482
+ });
321
483
  });
@@ -1,15 +1,23 @@
1
1
  "use client";
2
2
 
3
- import { type CSSProperties, useEffect, useRef, useState } from "react";
4
3
  import {
4
+ type CSSProperties,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import {
11
+ isShimLoadError,
5
12
  type RenderedFrame,
6
13
  SafeContentFrame,
7
14
  type SandboxOption,
8
15
  } from "safe-content-frame";
9
- import { invokeCallbackSafely } from "../utils/invokeCallbackSafely";
16
+ import { invokeUserCallback } from "@assistant-ui/core/internal";
10
17
 
11
18
  const DEFAULT_PRODUCT = "assistant-ui-sandbox";
12
19
  const DEFAULT_MAX_HEIGHT = 800;
20
+ const LOAD_TIMEOUT_MS = 10_000;
13
21
 
14
22
  export type SandboxHostConfig = {
15
23
  sandbox?: SandboxOption[];
@@ -81,8 +89,15 @@ export function SandboxHost({
81
89
  undefined,
82
90
  );
83
91
 
84
- const liveRef = useRef<LiveSnapshot>(null!);
85
- liveRef.current = { content, sandbox, createBridge, onError };
92
+ const liveRef = useRef<LiveSnapshot>({
93
+ content,
94
+ sandbox,
95
+ createBridge,
96
+ onError,
97
+ });
98
+ useLayoutEffect(() => {
99
+ liveRef.current = { content, sandbox, createBridge, onError };
100
+ }, [content, sandbox, createBridge, onError]);
86
101
 
87
102
  useEffect(() => {
88
103
  const container = containerRef.current;
@@ -95,6 +110,16 @@ export function SandboxHost({
95
110
 
96
111
  const { content: liveContent, sandbox: sb } = liveRef.current;
97
112
 
113
+ const reportError = (err: unknown) => {
114
+ const error = err instanceof Error ? err : new Error(String(err));
115
+ invokeUserCallback(
116
+ "assistant-ui",
117
+ "SandboxHost onError",
118
+ liveRef.current.onError?.bind(liveRef.current),
119
+ error,
120
+ );
121
+ };
122
+
98
123
  const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {
99
124
  ...(sb?.sandbox !== undefined && { sandbox: sb.sandbox }),
100
125
  ...(sb?.useShadowDom !== undefined && { useShadowDom: sb.useShadowDom }),
@@ -146,16 +171,24 @@ export function SandboxHost({
146
171
  bridge?.onMessage(event);
147
172
  };
148
173
  window.addEventListener("message", onMessage);
174
+
175
+ // renderHtml resolves at iframe load, which a shim that was never
176
+ // served also reaches, so a completed render is only known once the
177
+ // frame says so. render-timeout is excluded because the shim is
178
+ // running and the guest may still paint, and reporting it would leave
179
+ // a host that renders an error state showing one over a frame that
180
+ // recovers.
181
+ rendered.fullyLoadedPromiseWithTimeout(LOAD_TIMEOUT_MS).catch((err) => {
182
+ if (cancelled) return;
183
+ if (isShimLoadError(err) && err.code === "render-timeout") return;
184
+ reportError(err);
185
+ });
149
186
  })
150
187
  .catch((err) => {
151
188
  if (cancelled) return;
152
189
  frame?.dispose();
153
190
  frame = null;
154
- const error = err instanceof Error ? err : new Error(String(err));
155
- invokeCallbackSafely(
156
- () => liveRef.current.onError?.(error),
157
- "SandboxHost onError",
158
- );
191
+ reportError(err);
159
192
  });
160
193
 
161
194
  return () => {
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment jsdom
2
2
 
3
3
  import { act, render, waitFor } from "@testing-library/react";
4
- import type { FC, PropsWithChildren } from "react";
4
+ import { useState, type FC, type PropsWithChildren } from "react";
5
5
  import { describe, expect, it } from "vitest";
6
6
  import {
7
7
  RuntimeAdapterProvider,
@@ -107,6 +107,36 @@ describe("RemoteThreadListAdapter.unstable_Provider", () => {
107
107
  expect(capture.adapters?.history).toBe(dummyHistory);
108
108
  });
109
109
 
110
+ it("picks up a post-mount useAdapters bag", async () => {
111
+ const capture: { adapters: CapturedAdapters } = { adapters: null };
112
+ const firstHistory: ThreadHistoryAdapter = {
113
+ load: async () => ({ messages: [] }),
114
+ append: async () => {},
115
+ };
116
+ const secondHistory: ThreadHistoryAdapter = {
117
+ load: async () => ({ messages: [] }),
118
+ append: async () => {},
119
+ };
120
+
121
+ let bump: (() => void) | undefined;
122
+ const adapter = makeAdapter({
123
+ unstable_useAdapters: function useTestAdapters() {
124
+ const [swapped, setSwapped] = useState(false);
125
+ bump = () => setSwapped(true);
126
+ return { history: swapped ? secondHistory : firstHistory };
127
+ },
128
+ });
129
+
130
+ await renderAndWaitForBinder(adapter, capture);
131
+ expect(capture.adapters?.history).toBe(firstHistory);
132
+
133
+ await act(async () => {
134
+ bump!();
135
+ });
136
+
137
+ expect(capture.adapters?.history).toBe(secondHistory);
138
+ });
139
+
110
140
  it("picks up a swapped Provider on re-render", async () => {
111
141
  const capture: { adapters: CapturedAdapters } = { adapters: null };
112
142
  const firstHistory: ThreadHistoryAdapter = {