@assistant-ui/react 0.15.15 → 0.15.17

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 (267) hide show
  1. package/README.md +3 -3
  2. package/dist/devtools/DevToolsHooks.d.ts +0 -2
  3. package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
  4. package/dist/devtools/DevToolsHooks.js +10 -12
  5. package/dist/devtools/DevToolsHooks.js.map +1 -1
  6. package/dist/index.d.ts +4 -3
  7. package/dist/index.js +2 -2
  8. package/dist/mcp-apps/McpAppRenderer.d.ts +6 -1
  9. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  10. package/dist/mcp-apps/McpAppRenderer.js +20 -13
  11. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  12. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  13. package/dist/mcp-apps/McpAppsRemoteHost.js +20 -17
  14. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  15. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  16. package/dist/mcp-apps/app-frame.js +189 -94
  17. package/dist/mcp-apps/app-frame.js.map +1 -1
  18. package/dist/mcp-apps/bridge.d.ts.map +1 -1
  19. package/dist/mcp-apps/bridge.js +2 -8
  20. package/dist/mcp-apps/bridge.js.map +1 -1
  21. package/dist/mcp-apps/index.d.ts +2 -2
  22. package/dist/model-context/frame/useAssistantFrameHost.d.ts +1 -1
  23. package/dist/model-context/frame/useAssistantFrameHost.js +10 -11
  24. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  25. package/dist/model-context/makeAssistantVisible.js +1 -1
  26. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  27. package/dist/primitives/actionBar/ActionBarCopy.js +3 -3
  28. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  29. package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -1
  30. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +7 -4
  31. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  32. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +3 -3
  33. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  34. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +3 -3
  35. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  36. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  37. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +3 -3
  38. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  39. package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
  40. package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
  41. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
  42. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
  43. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
  44. package/dist/primitives/actionBarMore/scope.d.ts +2 -2
  45. package/dist/primitives/actionBarMore/scope.d.ts.map +1 -1
  46. package/dist/primitives/actionBarMore/scope.js.map +1 -1
  47. package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
  48. package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
  49. package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
  50. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  51. package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
  52. package/dist/primitives/assistantModal/scope.d.ts +2 -2
  53. package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
  54. package/dist/primitives/assistantModal/scope.js.map +1 -1
  55. package/dist/primitives/branchPicker/BranchPickerRoot.d.ts.map +1 -1
  56. package/dist/primitives/branchPicker/BranchPickerRoot.js +20 -15
  57. package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
  58. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  59. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  60. package/dist/primitives/composer/ComposerDictationTranscript.d.ts +2 -2
  61. package/dist/primitives/composer/ComposerDictationTranscript.js +2 -2
  62. package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
  63. package/dist/primitives/composer/ComposerInput.js +1 -3
  64. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  65. package/dist/primitives/composer/ComposerQuote.js +5 -5
  66. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  67. package/dist/primitives/composer/ComposerRoot.js +3 -3
  68. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  69. package/dist/primitives/composer/trigger/TriggerPopover.d.ts +5 -0
  70. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  71. package/dist/primitives/composer/trigger/TriggerPopover.js +157 -130
  72. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  73. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +3 -3
  74. package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
  75. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +4 -4
  76. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
  77. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +4 -4
  78. package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
  79. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -0
  80. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  81. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +63 -61
  82. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  83. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +2 -0
  84. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
  85. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
  86. package/dist/primitives/composer/trigger/detectTrigger.d.ts +21 -1
  87. package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
  88. package/dist/primitives/composer/trigger/detectTrigger.js +17 -3
  89. package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
  90. package/dist/primitives/composer/trigger/index.d.ts +3 -1
  91. package/dist/primitives/composer/trigger/index.d.ts.map +1 -1
  92. package/dist/primitives/composer/trigger/index.js.map +1 -1
  93. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +3 -4
  94. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  95. package/dist/primitives/composer/trigger/triggerDetectionResource.js +15 -14
  96. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  97. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  98. package/dist/primitives/composer/trigger/triggerSelectionResource.js +10 -7
  99. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  100. package/dist/primitives/composer.d.ts +3 -2
  101. package/dist/primitives/composer.js.map +1 -1
  102. package/dist/primitives/error/ErrorMessage.js.map +1 -1
  103. package/dist/primitives/message/MessageIf.js.map +1 -1
  104. package/dist/primitives/message/MessageParts.js.map +1 -1
  105. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  106. package/dist/primitives/message/MessageRoot.js +4 -4
  107. package/dist/primitives/message/MessageRoot.js.map +1 -1
  108. package/dist/primitives/message.js +1 -1
  109. package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
  110. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +3 -3
  111. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  112. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +3 -3
  113. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  114. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  115. package/dist/primitives/thread/ThreadRoot.d.ts +3 -0
  116. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  117. package/dist/primitives/thread/ThreadRoot.js +3 -0
  118. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  119. package/dist/primitives/thread/ThreadViewport.js +3 -3
  120. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  121. package/dist/primitives/thread/ThreadViewportFooter.js +3 -3
  122. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  123. package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -1
  124. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  125. package/dist/primitives/threadList/ThreadListNew.js +3 -3
  126. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  127. package/dist/primitives/threadListFocusGroup.d.ts +2 -1
  128. package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
  129. package/dist/primitives/threadListFocusGroup.js +2 -2
  130. package/dist/primitives/threadListFocusGroup.js.map +1 -1
  131. package/dist/primitives/threadListItem/ThreadListItemRoot.js +3 -3
  132. package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
  133. package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -1
  134. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  135. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
  136. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
  137. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
  138. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -1
  139. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
  140. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
  141. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
  142. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
  143. package/dist/primitives/threadListItemMore/scope.d.ts +2 -2
  144. package/dist/primitives/threadListItemMore/scope.d.ts.map +1 -1
  145. package/dist/primitives/threadListItemMore/scope.js.map +1 -1
  146. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  147. package/dist/sandbox-host/SandboxHost.js +138 -68
  148. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  149. package/dist/unstable/useComposerInputHistory.js +1 -1
  150. package/dist/unstable/useLiveCompletionAdapter.js +3 -1
  151. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  152. package/dist/unstable/useSlashCommandAdapter.js +41 -27
  153. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  154. package/dist/utils/Primitive.d.ts +2 -2
  155. package/dist/utils/Primitive.js +2 -2
  156. package/dist/utils/Primitive.js.map +1 -1
  157. package/dist/utils/createActionButton.js +3 -3
  158. package/dist/utils/createActionButton.js.map +1 -1
  159. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  160. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  161. package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
  162. package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
  163. package/dist/utils/invokeCallbackSafely.d.ts +5 -0
  164. package/dist/utils/invokeCallbackSafely.d.ts.map +1 -0
  165. package/dist/utils/invokeCallbackSafely.js +15 -0
  166. package/dist/utils/invokeCallbackSafely.js.map +1 -0
  167. package/dist/utils/smooth/SmoothContext.d.ts +30 -6
  168. package/dist/utils/smooth/SmoothContext.d.ts.map +1 -1
  169. package/dist/utils/smooth/useSmooth.d.ts +3 -0
  170. package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
  171. package/dist/utils/smooth/useSmooth.js +17 -2
  172. package/dist/utils/smooth/useSmooth.js.map +1 -1
  173. package/dist/utils/useToolArgsFieldStatus.d.ts +5 -1
  174. package/package.json +12 -20
  175. package/src/devtools/DevToolsHooks.test.ts +40 -0
  176. package/src/devtools/DevToolsHooks.ts +10 -15
  177. package/src/index.ts +9 -1
  178. package/src/mcp-apps/McpAppRenderer.test.tsx +133 -4
  179. package/src/mcp-apps/McpAppRenderer.tsx +28 -21
  180. package/src/mcp-apps/McpAppsRemoteHost.concurrent.test.tsx +215 -0
  181. package/src/mcp-apps/McpAppsRemoteHost.ts +28 -12
  182. package/src/mcp-apps/app-frame.test.tsx +102 -0
  183. package/src/mcp-apps/app-frame.tsx +17 -4
  184. package/src/mcp-apps/bridge.test.ts +21 -0
  185. package/src/mcp-apps/bridge.ts +2 -14
  186. package/src/mcp-apps/index.ts +1 -0
  187. package/src/model-context/frame/AssistantFrame.test.ts +3 -3
  188. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
  189. package/src/model-context/frame/useAssistantFrameHost.ts +2 -2
  190. package/src/model-context/makeAssistantVisible.tsx +1 -1
  191. package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
  192. package/src/primitives/actionBar/ActionBarExportMarkdown.test.tsx +67 -0
  193. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +4 -2
  194. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
  195. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
  196. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +1 -1
  197. package/src/primitives/actionBarMore/scope.ts +2 -2
  198. package/src/primitives/assistantModal/AssistantModalContent.tsx +1 -1
  199. package/src/primitives/assistantModal/scope.ts +2 -2
  200. package/src/primitives/branchPicker/BranchPickerRoot.tsx +5 -3
  201. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
  202. package/src/primitives/composer/ComposerDictationTranscript.tsx +2 -2
  203. package/src/primitives/composer/ComposerInput.test.tsx +2 -1
  204. package/src/primitives/composer/ComposerInput.tsx +3 -3
  205. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  206. package/src/primitives/composer/ComposerRoot.tsx +1 -1
  207. package/src/primitives/composer/trigger/TriggerPopover.tsx +16 -1
  208. package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +1 -1
  209. package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +1 -1
  210. package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +1 -1
  211. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +4 -1
  212. package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +2 -0
  213. package/src/primitives/composer/trigger/detectTrigger.test.ts +88 -5
  214. package/src/primitives/composer/trigger/detectTrigger.ts +50 -9
  215. package/src/primitives/composer/trigger/index.ts +1 -0
  216. package/src/primitives/composer/trigger/triggerDetectionResource.ts +10 -7
  217. package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +147 -0
  218. package/src/primitives/composer/trigger/triggerSelectionResource.ts +3 -4
  219. package/src/primitives/composer.ts +1 -0
  220. package/src/primitives/message/MessageRoot.tsx +1 -1
  221. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  222. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +1 -1
  223. package/src/primitives/thread/ThreadRoot.tsx +3 -0
  224. package/src/primitives/thread/ThreadViewport.tsx +1 -1
  225. package/src/primitives/thread/ThreadViewportFooter.tsx +1 -1
  226. package/src/primitives/thread/useThreadViewportAutoScroll.ts +1 -1
  227. package/src/primitives/threadList/ThreadListNew.tsx +1 -1
  228. package/src/primitives/threadListFocusGroup.ts +2 -2
  229. package/src/primitives/threadListItem/ThreadListItemRoot.tsx +1 -1
  230. package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +1 -1
  231. package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +1 -1
  232. package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +1 -1
  233. package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +2 -2
  234. package/src/primitives/threadListItemMore/scope.ts +2 -2
  235. package/src/sandbox-host/SandboxHost.test.tsx +106 -1
  236. package/src/sandbox-host/SandboxHost.tsx +22 -5
  237. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +31 -1
  238. package/src/unstable/useLiveCompletionAdapter.test.tsx +117 -1
  239. package/src/unstable/useLiveCompletionAdapter.ts +4 -1
  240. package/src/unstable/useSlashCommandAdapter.test.tsx +141 -0
  241. package/src/unstable/useSlashCommandAdapter.ts +52 -17
  242. package/src/utils/Primitive.tsx +2 -2
  243. package/src/utils/createActionButton.tsx +1 -1
  244. package/src/utils/hooks/useOnResizeContent.ts +1 -1
  245. package/src/utils/hooks/useOnScrollToBottom.ts +1 -1
  246. package/src/utils/invokeCallbackSafely.ts +11 -0
  247. package/src/utils/smooth/useSmooth.test.tsx +110 -0
  248. package/src/utils/smooth/useSmooth.ts +21 -7
  249. package/dist/context/react/utils/ensureBinding.d.ts +0 -5
  250. package/dist/context/react/utils/ensureBinding.d.ts.map +0 -1
  251. package/dist/context/react/utils/ensureBinding.js +0 -24
  252. package/dist/context/react/utils/ensureBinding.js.map +0 -1
  253. package/dist/context/react/utils/useRuntimeState.d.ts +0 -13
  254. package/dist/context/react/utils/useRuntimeState.d.ts.map +0 -1
  255. package/dist/context/react/utils/useRuntimeState.js +0 -33
  256. package/dist/context/react/utils/useRuntimeState.js.map +0 -1
  257. package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +0 -3
  258. package/dist/legacy-runtime/runtime/MessagePartRuntime.js +0 -2
  259. package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +0 -3
  260. package/dist/legacy-runtime/runtime/MessageRuntime.js +0 -2
  261. package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +0 -3
  262. package/dist/legacy-runtime/runtime/ThreadListRuntime.js +0 -2
  263. package/src/context/react/utils/ensureBinding.ts +0 -46
  264. package/src/context/react/utils/useRuntimeState.ts +0 -46
  265. package/src/legacy-runtime/runtime/MessagePartRuntime.ts +0 -2
  266. package/src/legacy-runtime/runtime/MessageRuntime.ts +0 -3
  267. package/src/legacy-runtime/runtime/ThreadListRuntime.ts +0 -4
@@ -222,4 +222,114 @@ describe("useSmooth", () => {
222
222
  frame();
223
223
  expect(result.current.text).toBe("z");
224
224
  });
225
+
226
+ it("drains completed text when a queued frame never runs", () => {
227
+ vi.spyOn(globalThis, "requestAnimationFrame").mockReturnValue(1);
228
+ vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
229
+
230
+ const { result, rerender } = renderHook((state) => useSmooth(state, true), {
231
+ initialProps: runningState(""),
232
+ });
233
+
234
+ rerender(runningState("hello"));
235
+ rerender(textState("hello"));
236
+
237
+ expect(result.current.text).toBe("hello");
238
+ expect(result.current.status.type).toBe("complete");
239
+ });
240
+
241
+ it("keeps draining after the source settles when frames are flowing", () => {
242
+ const raf: FrameRequestCallback[] = [];
243
+ vi.spyOn(globalThis, "requestAnimationFrame").mockImplementation((cb) => {
244
+ raf.push(cb);
245
+ return raf.length;
246
+ });
247
+ vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
248
+ let now = 1000;
249
+ vi.spyOn(Date, "now").mockImplementation(() => now);
250
+
251
+ const frame = () => {
252
+ if (raf.length === 0) return;
253
+ const cb = raf.shift()!;
254
+ now += 16;
255
+ act(() => {
256
+ cb(now);
257
+ });
258
+ };
259
+
260
+ const { result, rerender } = renderHook(
261
+ (state) =>
262
+ useSmooth(state, {
263
+ maxCharsPerFrame: 1,
264
+ maxCharIntervalMs: 1,
265
+ drainMs: 250,
266
+ }),
267
+ { initialProps: runningState("hello") },
268
+ );
269
+
270
+ frame();
271
+ const mid = result.current.text;
272
+ expect(mid.length).toBeGreaterThan(0);
273
+ expect(mid.length).toBeLessThan("hello".length);
274
+ expect(result.current.status.type).toBe("running");
275
+
276
+ rerender(textState("hello"));
277
+ expect(result.current.text).toBe(mid);
278
+ expect(result.current.status.type).toBe("running");
279
+
280
+ frame();
281
+ expect(result.current.text.length).toBeGreaterThan(mid.length);
282
+
283
+ let guard = 0;
284
+ while (result.current.text !== "hello" && guard < 20) {
285
+ guard++;
286
+ frame();
287
+ }
288
+ expect(result.current.text).toBe("hello");
289
+ expect(result.current.status.type).toBe("complete");
290
+ });
291
+
292
+ it("snaps when a settled part replaces the text instead of looping the animator", () => {
293
+ const raf: FrameRequestCallback[] = [];
294
+ vi.spyOn(globalThis, "requestAnimationFrame").mockImplementation((cb) => {
295
+ raf.push(cb);
296
+ return raf.length;
297
+ });
298
+ vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
299
+ let now = 1000;
300
+ vi.spyOn(Date, "now").mockImplementation(() => now);
301
+
302
+ const frame = () => {
303
+ if (raf.length === 0) return;
304
+ const cb = raf.shift()!;
305
+ now += 16;
306
+ act(() => {
307
+ cb(now);
308
+ });
309
+ };
310
+
311
+ const { result, rerender } = renderHook(
312
+ (state) =>
313
+ useSmooth(state, {
314
+ maxCharsPerFrame: 1,
315
+ maxCharIntervalMs: 1,
316
+ drainMs: 250,
317
+ }),
318
+ { initialProps: runningState("hello world") },
319
+ );
320
+
321
+ frame();
322
+ expect(result.current.text.length).toBeGreaterThan(0);
323
+ const queued = raf.length;
324
+
325
+ rerender(textState("hi"));
326
+ expect(result.current.text).toBe("hi");
327
+ expect(result.current.status.type).toBe("complete");
328
+ expect(raf.length).toBeLessThanOrEqual(queued);
329
+
330
+ const after = raf.length;
331
+ frame();
332
+ expect(result.current.text).toBe("hi");
333
+ expect(raf.length).toBeLessThanOrEqual(after);
334
+ });
225
335
  });
@@ -8,7 +8,7 @@ import type {
8
8
  TextMessagePart,
9
9
  MessagePartState,
10
10
  } from "@assistant-ui/core";
11
- import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
11
+ import { useCallbackRef } from "radix-ui/internal";
12
12
  import { useMediaQuery } from "../hooks/useMediaQuery";
13
13
  import { useSmoothStatusStore } from "./SmoothContext";
14
14
  import { writableStore } from "../../context/ReadonlyStore";
@@ -137,6 +137,9 @@ const positiveOr = (value: number | undefined, fallback: number): number =>
137
137
  * `true` uses the default rate, and a {@link SmoothOptions} object tunes
138
138
  * the reveal. Returns the part state with `text` replaced by the revealed
139
139
  * prefix and `status` reporting `running` until the reveal catches up.
140
+ * If the source settles before any character has been revealed, the text
141
+ * is committed immediately so a missed animation frame cannot leave an
142
+ * empty bubble.
140
143
  *
141
144
  * The reveal auto-disables under `prefers-reduced-motion: reduce`,
142
145
  * committing the full text immediately; this takes precedence over an
@@ -229,13 +232,10 @@ export const useSmooth = (
229
232
  return;
230
233
  }
231
234
 
232
- // Discontinuity: part flipped, or new text breaks continuation
233
- // of the animator's current target. Either case requires
234
- // resetting the cursor — without the part check, a new part
235
- // whose text happens to share a prefix with the previous target
236
- // would keep the stale cursor and flicker.
237
235
  const partChanged = animatorPartRef.current !== part;
238
236
  animatorPartRef.current = part;
237
+ // A new part whose text shares a prefix with the previous target would
238
+ // keep the stale cursor and flicker without this reset.
239
239
  if (partChanged || !text.startsWith(animatorRef.targetText)) {
240
240
  if (state.status.type === "running") {
241
241
  animatorRef.currentText = "";
@@ -246,13 +246,27 @@ export const useSmooth = (
246
246
  animatorRef.currentText = text;
247
247
  animatorRef.targetText = text;
248
248
  animatorRef.stop();
249
+ setText(text);
249
250
  }
250
251
  return;
251
252
  }
252
253
 
253
254
  animatorRef.targetText = text;
255
+ if (state.status.type !== "running") {
256
+ // No character has painted. A pending frame that never runs would
257
+ // leave an empty bubble after settle.
258
+ if (animatorRef.currentText === "") {
259
+ animatorRef.currentText = text;
260
+ animatorRef.stop();
261
+ setText(text);
262
+ return;
263
+ }
264
+ animatorRef.start();
265
+ return;
266
+ }
267
+
254
268
  animatorRef.start();
255
- }, [animatorRef, enabled, text, state.status.type, part]);
269
+ }, [animatorRef, enabled, text, state.status.type, part, setText]);
256
270
 
257
271
  useEffect(() => {
258
272
  return () => {
@@ -1,5 +0,0 @@
1
- //#region src/context/react/utils/ensureBinding.d.ts
2
- declare const ensureBinding: (r: unknown) => void;
3
- //#endregion
4
- export { ensureBinding };
5
- //# sourceMappingURL=ensureBinding.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ensureBinding.d.ts","names":[],"sources":["../../../../src/context/react/utils/ensureBinding.ts"],"mappings":";cAmCa,gBAAiB"}
@@ -1,24 +0,0 @@
1
- //#region src/context/react/utils/ensureBinding.ts
2
- const debugVerifyPrototype = (runtime, prototype) => {
3
- const unboundMethods = Object.getOwnPropertyNames(prototype).filter((methodStr) => {
4
- const descriptor = Object.getOwnPropertyDescriptor(prototype, methodStr);
5
- const isMethod = descriptor && typeof descriptor.value === "function";
6
- if (!isMethod) return false;
7
- const methodName = methodStr;
8
- return isMethod && !methodName.startsWith("_") && methodName !== "constructor" && prototype[methodName] === runtime[methodName];
9
- });
10
- if (unboundMethods.length > 0) throw new Error(`The following methods are not bound: ${JSON.stringify(unboundMethods)}`);
11
- const prototypePrototype = Object.getPrototypeOf(prototype);
12
- if (prototypePrototype && prototypePrototype !== Object.prototype) debugVerifyPrototype(runtime, prototypePrototype);
13
- };
14
- const ensureBinding = (r) => {
15
- const runtime = r;
16
- if (runtime.__isBound) return;
17
- runtime.__internal_bindMethods?.();
18
- runtime.__isBound = true;
19
- if (process.env.NODE_ENV !== "production") debugVerifyPrototype(runtime, Object.getPrototypeOf(runtime));
20
- };
21
- //#endregion
22
- export { ensureBinding };
23
-
24
- //# sourceMappingURL=ensureBinding.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ensureBinding.js","names":["Bindable","__internal_bindMethods","__isBound","debugVerifyPrototype","runtime","Record","prototype","unboundMethods","Object","getOwnPropertyNames","filter","methodStr","descriptor","getOwnPropertyDescriptor","isMethod","value","methodName","startsWith","length","Error","JSON","stringify","prototypePrototype","getPrototypeOf","ensureBinding","r","process","env","NODE_ENV"],"sources":["../../../../src/context/react/utils/ensureBinding.ts"],"sourcesContent":["type Bindable = {\n __internal_bindMethods?: () => void;\n __isBound?: boolean;\n};\nconst debugVerifyPrototype = (\n runtime: Record<string, unknown>,\n prototype: any,\n) => {\n const unboundMethods = Object.getOwnPropertyNames(prototype).filter(\n (methodStr) => {\n const descriptor = Object.getOwnPropertyDescriptor(prototype, methodStr);\n const isMethod = descriptor && typeof descriptor.value === \"function\";\n if (!isMethod) return false;\n\n const methodName = methodStr as keyof typeof runtime | \"constructor\";\n return (\n isMethod &&\n !methodName.startsWith(\"_\") &&\n methodName !== \"constructor\" &&\n prototype[methodName] === runtime[methodName]\n );\n },\n );\n\n if (unboundMethods.length > 0) {\n throw new Error(\n `The following methods are not bound: ${JSON.stringify(unboundMethods)}`,\n );\n }\n\n const prototypePrototype = Object.getPrototypeOf(prototype);\n if (prototypePrototype && prototypePrototype !== Object.prototype) {\n debugVerifyPrototype(runtime, prototypePrototype);\n }\n};\nexport const ensureBinding = (r: unknown) => {\n const runtime = r as Bindable;\n if (runtime.__isBound) return;\n\n runtime.__internal_bindMethods?.();\n runtime.__isBound = true;\n\n if (process.env.NODE_ENV !== \"production\") {\n debugVerifyPrototype(runtime, Object.getPrototypeOf(runtime));\n }\n};\n"],"mappings":";AAIA,MAAMG,wBACJC,SACAE,cACG;CACH,MAAMC,iBAAiBC,OAAOC,oBAAoBH,SAAS,CAAC,CAACI,QAC1DC,cAAc;EACb,MAAMC,aAAaJ,OAAOK,yBAAyBP,WAAWK,SAAS;EACvE,MAAMG,WAAWF,cAAc,OAAOA,WAAWG,UAAU;EAC3D,IAAI,CAACD,UAAU,OAAO;EAEtB,MAAME,aAAaL;EACnB,OACEG,YACA,CAACE,WAAWC,WAAW,GAAG,KAC1BD,eAAe,iBACfV,UAAUU,gBAAgBZ,QAAQY;CAEtC,CACF;CAEA,IAAIT,eAAeW,SAAS,GAC1B,MAAM,IAAIC,MACR,wCAAwCC,KAAKC,UAAUd,cAAc,GACvE;CAGF,MAAMe,qBAAqBd,OAAOe,eAAejB,SAAS;CAC1D,IAAIgB,sBAAsBA,uBAAuBd,OAAOF,WACtDH,qBAAqBC,SAASkB,kBAAkB;AAEpD;AACA,MAAaE,iBAAiBC,MAAe;CAC3C,MAAMrB,UAAUqB;CAChB,IAAIrB,QAAQF,WAAW;CAEvBE,QAAQH,yBAAyB;CACjCG,QAAQF,YAAY;CAEpB,IAAIwB,QAAQC,IAAIC,aAAa,cAC3BzB,qBAAqBC,SAASI,OAAOe,eAAenB,OAAO,CAAC;AAEhE"}
@@ -1,13 +0,0 @@
1
- import { Unsubscribe } from "@assistant-ui/core";
2
- //#region src/context/react/utils/useRuntimeState.d.ts
3
- type SubscribableRuntime<TState> = {
4
- getState: () => TState;
5
- subscribe: (callback: () => void) => Unsubscribe;
6
- };
7
- declare function useRuntimeStateInternal<TState, TSelected>(runtime: SubscribableRuntime<TState>, selector?: ((state: TState) => TSelected | TState) | undefined): TSelected | TState;
8
- declare function useRuntimeState<TState>(runtime: SubscribableRuntime<TState>): TState;
9
- declare function useRuntimeState<TState, TSelected>(runtime: SubscribableRuntime<TState>, selector: (state: TState) => TSelected): TSelected;
10
- declare function useRuntimeState<TState, TSelected>(runtime: SubscribableRuntime<TState>, selector: ((state: TState) => TSelected) | undefined): TSelected | TState;
11
- //#endregion
12
- export { SubscribableRuntime, useRuntimeState, useRuntimeStateInternal };
13
- //# sourceMappingURL=useRuntimeState.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRuntimeState.d.ts","names":[],"sources":["../../../../src/context/react/utils/useRuntimeState.ts"],"mappings":";;KAIY,oBAAoB;EAC9B,gBAAgB;EAChB,YAAY,yBAAyB;;iBAGvB,wBAAwB,QAAQ,WAC9C,SAAS,oBAAoB,SAC7B,aAAY,OAAO,WAAW,YAAY,sBACzC,YAAY;iBAcC,gBAAgB,QAC9B,SAAS,oBAAoB,UAC5B;iBACa,gBAAgB,QAAQ,WACtC,SAAS,oBAAoB,SAC7B,WAAW,OAAO,WAAW,YAC5B;iBACa,gBAAgB,QAAQ,WACtC,SAAS,oBAAoB,SAC7B,YAAY,OAAO,WAAW,yBAC7B,YAAY"}
@@ -1,33 +0,0 @@
1
- import { ensureBinding } from "./ensureBinding.js";
2
- import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
3
- import { useDebugValue, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
4
- //#region src/context/react/utils/useRuntimeState.ts
5
- function useRuntimeStateInternal(runtime, t0) {
6
- const $ = c(4);
7
- const selector = t0 === void 0 ? identity : t0;
8
- ensureBinding(runtime);
9
- let t1;
10
- let t2;
11
- if ($[0] !== runtime || $[1] !== selector) {
12
- t1 = () => selector(runtime.getState());
13
- t2 = () => selector(runtime.getState());
14
- $[0] = runtime;
15
- $[1] = selector;
16
- $[2] = t1;
17
- $[3] = t2;
18
- } else {
19
- t1 = $[2];
20
- t2 = $[3];
21
- }
22
- const slice = useSyncExternalStore(runtime.subscribe, t1, t2);
23
- useDebugValue(slice);
24
- return slice;
25
- }
26
- const identity = (arg) => arg;
27
- function useRuntimeState(runtime, selector) {
28
- return useRuntimeStateInternal(runtime, selector);
29
- }
30
- //#endregion
31
- export { useRuntimeState, useRuntimeStateInternal };
32
-
33
- //# sourceMappingURL=useRuntimeState.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRuntimeState.js","names":["Unsubscribe","useDebugValue","useSyncExternalStore","ensureBinding","SubscribableRuntime","getState","TState","subscribe","callback","useRuntimeStateInternal","runtime","t0","$","_c","selector","undefined","identity","t1","t2","slice","arg","T","useRuntimeState","state","TSelected"],"sources":["../../../../src/context/react/utils/useRuntimeState.ts"],"sourcesContent":["import type { Unsubscribe } from \"@assistant-ui/core\";\nimport { useDebugValue, useSyncExternalStore } from \"react\";\nimport { ensureBinding } from \"./ensureBinding\";\n\nexport type SubscribableRuntime<TState> = {\n getState: () => TState;\n subscribe: (callback: () => void) => Unsubscribe;\n};\n\nexport function useRuntimeStateInternal<TState, TSelected>(\n runtime: SubscribableRuntime<TState>,\n selector: ((state: TState) => TSelected | TState) | undefined = identity,\n): TSelected | TState {\n // TODO move to useRuntimeState\n ensureBinding(runtime);\n\n const slice = useSyncExternalStore(\n runtime.subscribe,\n () => selector(runtime.getState()),\n () => selector(runtime.getState()),\n );\n useDebugValue(slice);\n return slice;\n}\n\nconst identity = <T>(arg: T): T => arg;\nexport function useRuntimeState<TState>(\n runtime: SubscribableRuntime<TState>,\n): TState;\nexport function useRuntimeState<TState, TSelected>(\n runtime: SubscribableRuntime<TState>,\n selector: (state: TState) => TSelected,\n): TSelected;\nexport function useRuntimeState<TState, TSelected>(\n runtime: SubscribableRuntime<TState>,\n selector: ((state: TState) => TSelected) | undefined,\n): TSelected | TState;\nexport function useRuntimeState<TState, TSelected>(\n runtime: SubscribableRuntime<TState>,\n selector?: ((state: TState) => TSelected) | undefined,\n): TSelected | TState {\n // ensure that the runtime is bound\n // ensureBinding(runtime);\n\n return useRuntimeStateInternal(runtime, selector);\n}\n"],"mappings":";;;;AASA,SAAOS,wBAAAC,SAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAEL,MAAAC,WAAAH,OAAAI,KAAAA,IAAAC,WAAAL;CAGAR,cAAcO,OAAO;CAAC,IAAAO;CAAA,IAAAC;CAAA,IAAAN,EAAA,OAAAF,WAAAE,EAAA,OAAAE,UAAA;EAIpBG,WAAMH,SAASJ,QAAOL,SAAU,CAAC;EACjCa,WAAMJ,SAASJ,QAAOL,SAAU,CAAC;EAACO,EAAA,KAAAF;EAAAE,EAAA,KAAAE;EAAAF,EAAA,KAAAK;EAAAL,EAAA,KAAAM;CAAA,OAAA;EAAAD,KAAAL,EAAA;EAAAM,KAAAN,EAAA;CAAA;CAHpC,MAAAO,QAAcjB,qBACZQ,QAAOH,WACPU,IACAC,EACF;CACAjB,cAAckB,KAAK;CAAC,OACbA;AAAK;AAGd,MAAMH,YAAeI,QAAcA;AAYnC,SAAOE,gBAAAZ,SAAAI,UAAA;CAAA,OAOEL,wBAAwBC,SAASI,QAAQ;AAAC"}
@@ -1,3 +0,0 @@
1
- import { MessagePartRuntime, MessagePartState } from "@assistant-ui/core";
2
- import { MessagePartRuntimeImpl } from "@assistant-ui/core/internal";
3
- export { type MessagePartRuntime, MessagePartRuntimeImpl, type MessagePartState };
@@ -1,2 +0,0 @@
1
- import { MessagePartRuntimeImpl } from "@assistant-ui/core/internal";
2
- export { MessagePartRuntimeImpl };
@@ -1,3 +0,0 @@
1
- import { MessageRuntime, MessageState } from "@assistant-ui/core";
2
- import { MessageRuntimeImpl, MessageStateBinding } from "@assistant-ui/core/internal";
3
- export { type MessageRuntime, MessageRuntimeImpl, type MessageState, type MessageStateBinding };
@@ -1,2 +0,0 @@
1
- import { MessageRuntimeImpl } from "@assistant-ui/core/internal";
2
- export { MessageRuntimeImpl };
@@ -1,3 +0,0 @@
1
- import { ThreadListRuntime, ThreadListState } from "@assistant-ui/core";
2
- import { ThreadListRuntimeCoreBinding, ThreadListRuntimeImpl } from "@assistant-ui/core/internal";
3
- export { type ThreadListRuntime, type ThreadListRuntimeCoreBinding, ThreadListRuntimeImpl, type ThreadListState };
@@ -1,2 +0,0 @@
1
- import { ThreadListRuntimeImpl } from "@assistant-ui/core/internal";
2
- export { ThreadListRuntimeImpl };
@@ -1,46 +0,0 @@
1
- type Bindable = {
2
- __internal_bindMethods?: () => void;
3
- __isBound?: boolean;
4
- };
5
- const debugVerifyPrototype = (
6
- runtime: Record<string, unknown>,
7
- prototype: any,
8
- ) => {
9
- const unboundMethods = Object.getOwnPropertyNames(prototype).filter(
10
- (methodStr) => {
11
- const descriptor = Object.getOwnPropertyDescriptor(prototype, methodStr);
12
- const isMethod = descriptor && typeof descriptor.value === "function";
13
- if (!isMethod) return false;
14
-
15
- const methodName = methodStr as keyof typeof runtime | "constructor";
16
- return (
17
- isMethod &&
18
- !methodName.startsWith("_") &&
19
- methodName !== "constructor" &&
20
- prototype[methodName] === runtime[methodName]
21
- );
22
- },
23
- );
24
-
25
- if (unboundMethods.length > 0) {
26
- throw new Error(
27
- `The following methods are not bound: ${JSON.stringify(unboundMethods)}`,
28
- );
29
- }
30
-
31
- const prototypePrototype = Object.getPrototypeOf(prototype);
32
- if (prototypePrototype && prototypePrototype !== Object.prototype) {
33
- debugVerifyPrototype(runtime, prototypePrototype);
34
- }
35
- };
36
- export const ensureBinding = (r: unknown) => {
37
- const runtime = r as Bindable;
38
- if (runtime.__isBound) return;
39
-
40
- runtime.__internal_bindMethods?.();
41
- runtime.__isBound = true;
42
-
43
- if (process.env.NODE_ENV !== "production") {
44
- debugVerifyPrototype(runtime, Object.getPrototypeOf(runtime));
45
- }
46
- };
@@ -1,46 +0,0 @@
1
- import type { Unsubscribe } from "@assistant-ui/core";
2
- import { useDebugValue, useSyncExternalStore } from "react";
3
- import { ensureBinding } from "./ensureBinding";
4
-
5
- export type SubscribableRuntime<TState> = {
6
- getState: () => TState;
7
- subscribe: (callback: () => void) => Unsubscribe;
8
- };
9
-
10
- export function useRuntimeStateInternal<TState, TSelected>(
11
- runtime: SubscribableRuntime<TState>,
12
- selector: ((state: TState) => TSelected | TState) | undefined = identity,
13
- ): TSelected | TState {
14
- // TODO move to useRuntimeState
15
- ensureBinding(runtime);
16
-
17
- const slice = useSyncExternalStore(
18
- runtime.subscribe,
19
- () => selector(runtime.getState()),
20
- () => selector(runtime.getState()),
21
- );
22
- useDebugValue(slice);
23
- return slice;
24
- }
25
-
26
- const identity = <T>(arg: T): T => arg;
27
- export function useRuntimeState<TState>(
28
- runtime: SubscribableRuntime<TState>,
29
- ): TState;
30
- export function useRuntimeState<TState, TSelected>(
31
- runtime: SubscribableRuntime<TState>,
32
- selector: (state: TState) => TSelected,
33
- ): TSelected;
34
- export function useRuntimeState<TState, TSelected>(
35
- runtime: SubscribableRuntime<TState>,
36
- selector: ((state: TState) => TSelected) | undefined,
37
- ): TSelected | TState;
38
- export function useRuntimeState<TState, TSelected>(
39
- runtime: SubscribableRuntime<TState>,
40
- selector?: ((state: TState) => TSelected) | undefined,
41
- ): TSelected | TState {
42
- // ensure that the runtime is bound
43
- // ensureBinding(runtime);
44
-
45
- return useRuntimeStateInternal(runtime, selector);
46
- }
@@ -1,2 +0,0 @@
1
- export type { MessagePartState, MessagePartRuntime } from "@assistant-ui/core";
2
- export { MessagePartRuntimeImpl } from "@assistant-ui/core/internal";
@@ -1,3 +0,0 @@
1
- export type { MessageState, MessageRuntime } from "@assistant-ui/core";
2
- export type { MessageStateBinding } from "@assistant-ui/core/internal";
3
- export { MessageRuntimeImpl } from "@assistant-ui/core/internal";
@@ -1,4 +0,0 @@
1
- export type { ThreadListState, ThreadListRuntime } from "@assistant-ui/core";
2
-
3
- export type { ThreadListRuntimeCoreBinding } from "@assistant-ui/core/internal";
4
- export { ThreadListRuntimeImpl } from "@assistant-ui/core/internal";