@assistant-ui/vue 0.0.0 → 0.0.1

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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -2
  3. package/dist/AuiIf.d.ts +27 -0
  4. package/dist/AuiIf.d.ts.map +1 -0
  5. package/dist/AuiIf.js +30 -0
  6. package/dist/AuiIf.js.map +1 -0
  7. package/dist/AuiProvider.d.ts +41 -0
  8. package/dist/AuiProvider.d.ts.map +1 -0
  9. package/dist/AuiProvider.js +61 -0
  10. package/dist/AuiProvider.js.map +1 -0
  11. package/dist/context.d.ts +10 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/context.js +17 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/index.d.ts +34 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +35 -0
  18. package/dist/primitives/AttachmentByIndexProvider.d.ts +30 -0
  19. package/dist/primitives/AttachmentByIndexProvider.d.ts.map +1 -0
  20. package/dist/primitives/AttachmentByIndexProvider.js +63 -0
  21. package/dist/primitives/AttachmentByIndexProvider.js.map +1 -0
  22. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.d.ts +11 -0
  23. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.d.ts.map +1 -0
  24. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.js +31 -0
  25. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.js.map +1 -0
  26. package/dist/primitives/ChainOfThoughtPrimitiveParts.d.ts +19 -0
  27. package/dist/primitives/ChainOfThoughtPrimitiveParts.d.ts.map +1 -0
  28. package/dist/primitives/ChainOfThoughtPrimitiveParts.js +85 -0
  29. package/dist/primitives/ChainOfThoughtPrimitiveParts.js.map +1 -0
  30. package/dist/primitives/ComposerPrimitiveCancel.d.ts +12 -0
  31. package/dist/primitives/ComposerPrimitiveCancel.d.ts.map +1 -0
  32. package/dist/primitives/ComposerPrimitiveCancel.js +33 -0
  33. package/dist/primitives/ComposerPrimitiveCancel.js.map +1 -0
  34. package/dist/primitives/ComposerPrimitiveInput.d.ts +26 -0
  35. package/dist/primitives/ComposerPrimitiveInput.d.ts.map +1 -0
  36. package/dist/primitives/ComposerPrimitiveInput.js +57 -0
  37. package/dist/primitives/ComposerPrimitiveInput.js.map +1 -0
  38. package/dist/primitives/ComposerPrimitiveSend.d.ts +13 -0
  39. package/dist/primitives/ComposerPrimitiveSend.d.ts.map +1 -0
  40. package/dist/primitives/ComposerPrimitiveSend.js +31 -0
  41. package/dist/primitives/ComposerPrimitiveSend.js.map +1 -0
  42. package/dist/primitives/MessageByIdProvider.d.ts +23 -0
  43. package/dist/primitives/MessageByIdProvider.d.ts.map +1 -0
  44. package/dist/primitives/MessageByIdProvider.js +70 -0
  45. package/dist/primitives/MessageByIdProvider.js.map +1 -0
  46. package/dist/primitives/MessagePrimitiveParts.d.ts +50 -0
  47. package/dist/primitives/MessagePrimitiveParts.d.ts.map +1 -0
  48. package/dist/primitives/MessagePrimitiveParts.js +93 -0
  49. package/dist/primitives/MessagePrimitiveParts.js.map +1 -0
  50. package/dist/primitives/PartByIndexProvider.d.ts +21 -0
  51. package/dist/primitives/PartByIndexProvider.d.ts.map +1 -0
  52. package/dist/primitives/PartByIndexProvider.js +50 -0
  53. package/dist/primitives/PartByIndexProvider.js.map +1 -0
  54. package/dist/primitives/ThreadListItemPrimitiveRoot.d.ts +7 -0
  55. package/dist/primitives/ThreadListItemPrimitiveRoot.d.ts.map +1 -0
  56. package/dist/primitives/ThreadListItemPrimitiveRoot.js +36 -0
  57. package/dist/primitives/ThreadListItemPrimitiveRoot.js.map +1 -0
  58. package/dist/primitives/ThreadListItemPrimitiveTrigger.d.ts +14 -0
  59. package/dist/primitives/ThreadListItemPrimitiveTrigger.d.ts.map +1 -0
  60. package/dist/primitives/ThreadListItemPrimitiveTrigger.js +58 -0
  61. package/dist/primitives/ThreadListItemPrimitiveTrigger.js.map +1 -0
  62. package/dist/primitives/ThreadListPrimitiveRoot.d.ts +7 -0
  63. package/dist/primitives/ThreadListPrimitiveRoot.d.ts.map +1 -0
  64. package/dist/primitives/ThreadListPrimitiveRoot.js +16 -0
  65. package/dist/primitives/ThreadListPrimitiveRoot.js.map +1 -0
  66. package/dist/primitives/ThreadPrimitiveMessages.d.ts +23 -0
  67. package/dist/primitives/ThreadPrimitiveMessages.d.ts.map +1 -0
  68. package/dist/primitives/ThreadPrimitiveMessages.js +37 -0
  69. package/dist/primitives/ThreadPrimitiveMessages.js.map +1 -0
  70. package/dist/primitives/ThreadPrimitiveScrollToBottom.d.ts +25 -0
  71. package/dist/primitives/ThreadPrimitiveScrollToBottom.d.ts.map +1 -0
  72. package/dist/primitives/ThreadPrimitiveScrollToBottom.js +43 -0
  73. package/dist/primitives/ThreadPrimitiveScrollToBottom.js.map +1 -0
  74. package/dist/primitives/ThreadPrimitiveViewport.d.ts +57 -0
  75. package/dist/primitives/ThreadPrimitiveViewport.d.ts.map +1 -0
  76. package/dist/primitives/ThreadPrimitiveViewport.js +174 -0
  77. package/dist/primitives/ThreadPrimitiveViewport.js.map +1 -0
  78. package/dist/primitives/ThreadPrimitiveViewportFooter.d.ts +13 -0
  79. package/dist/primitives/ThreadPrimitiveViewportFooter.d.ts.map +1 -0
  80. package/dist/primitives/ThreadPrimitiveViewportFooter.js +46 -0
  81. package/dist/primitives/ThreadPrimitiveViewportFooter.js.map +1 -0
  82. package/dist/primitives/actionBar.d.ts +41 -0
  83. package/dist/primitives/actionBar.d.ts.map +1 -0
  84. package/dist/primitives/actionBar.js +110 -0
  85. package/dist/primitives/actionBar.js.map +1 -0
  86. package/dist/primitives/attachment.d.ts +27 -0
  87. package/dist/primitives/attachment.d.ts.map +1 -0
  88. package/dist/primitives/attachment.js +63 -0
  89. package/dist/primitives/attachment.js.map +1 -0
  90. package/dist/primitives/attrDisabled.d.ts +2 -0
  91. package/dist/primitives/attrDisabled.d.ts.map +1 -0
  92. package/dist/primitives/attrDisabled.js +6 -0
  93. package/dist/primitives/attrDisabled.js.map +1 -0
  94. package/dist/primitives/branchPicker.d.ts +18 -0
  95. package/dist/primitives/branchPicker.d.ts.map +1 -0
  96. package/dist/primitives/branchPicker.js +48 -0
  97. package/dist/primitives/branchPicker.js.map +1 -0
  98. package/dist/primitives/composerAttachments.d.ts +57 -0
  99. package/dist/primitives/composerAttachments.d.ts.map +1 -0
  100. package/dist/primitives/composerAttachments.js +132 -0
  101. package/dist/primitives/composerAttachments.js.map +1 -0
  102. package/dist/primitives/error.d.ts +15 -0
  103. package/dist/primitives/error.d.ts.map +1 -0
  104. package/dist/primitives/error.js +29 -0
  105. package/dist/primitives/error.js.map +1 -0
  106. package/dist/primitives/lastValidCache.d.ts +6 -0
  107. package/dist/primitives/lastValidCache.d.ts.map +1 -0
  108. package/dist/primitives/lastValidCache.js +9 -0
  109. package/dist/primitives/lastValidCache.js.map +1 -0
  110. package/dist/primitives/message.d.ts +12 -0
  111. package/dist/primitives/message.d.ts.map +1 -0
  112. package/dist/primitives/message.js +44 -0
  113. package/dist/primitives/message.js.map +1 -0
  114. package/dist/primitives/messageAttachments.d.ts +12 -0
  115. package/dist/primitives/messageAttachments.d.ts.map +1 -0
  116. package/dist/primitives/messageAttachments.js +27 -0
  117. package/dist/primitives/messageAttachments.js.map +1 -0
  118. package/dist/primitives/stableKeys.d.ts +5 -0
  119. package/dist/primitives/stableKeys.d.ts.map +1 -0
  120. package/dist/primitives/stableKeys.js +14 -0
  121. package/dist/primitives/stableKeys.js.map +1 -0
  122. package/dist/primitives/suggestions.d.ts +66 -0
  123. package/dist/primitives/suggestions.d.ts.map +1 -0
  124. package/dist/primitives/suggestions.js +137 -0
  125. package/dist/primitives/suggestions.js.map +1 -0
  126. package/dist/primitives/thread.d.ts +11 -0
  127. package/dist/primitives/thread.d.ts.map +1 -0
  128. package/dist/primitives/thread.js +37 -0
  129. package/dist/primitives/thread.js.map +1 -0
  130. package/dist/primitives/threadList.d.ts +82 -0
  131. package/dist/primitives/threadList.d.ts.map +1 -0
  132. package/dist/primitives/threadList.js +128 -0
  133. package/dist/primitives/threadList.js.map +1 -0
  134. package/dist/primitives/threadListFocusGroup.d.ts +14 -0
  135. package/dist/primitives/threadListFocusGroup.d.ts.map +1 -0
  136. package/dist/primitives/threadListFocusGroup.js +32 -0
  137. package/dist/primitives/threadListFocusGroup.js.map +1 -0
  138. package/dist/primitives/threadListStructural.d.ts +29 -0
  139. package/dist/primitives/threadListStructural.d.ts.map +1 -0
  140. package/dist/primitives/threadListStructural.js +55 -0
  141. package/dist/primitives/threadListStructural.js.map +1 -0
  142. package/dist/primitives/useComposerSendState.d.ts +6 -0
  143. package/dist/primitives/useComposerSendState.d.ts.map +1 -0
  144. package/dist/primitives/useComposerSendState.js +15 -0
  145. package/dist/primitives/useComposerSendState.js.map +1 -0
  146. package/dist/primitives/viewportContext.d.ts +12 -0
  147. package/dist/primitives/viewportContext.d.ts.map +1 -0
  148. package/dist/primitives/viewportContext.js +6 -0
  149. package/dist/primitives/viewportContext.js.map +1 -0
  150. package/dist/useAui.d.ts +15 -0
  151. package/dist/useAui.d.ts.map +1 -0
  152. package/dist/useAui.js +21 -0
  153. package/dist/useAui.js.map +1 -0
  154. package/dist/useAuiEvent.d.ts +17 -0
  155. package/dist/useAuiEvent.d.ts.map +1 -0
  156. package/dist/useAuiEvent.js +52 -0
  157. package/dist/useAuiEvent.js.map +1 -0
  158. package/dist/useAuiState.d.ts +23 -0
  159. package/dist/useAuiState.d.ts.map +1 -0
  160. package/dist/useAuiState.js +42 -0
  161. package/dist/useAuiState.js.map +1 -0
  162. package/dist/useScrollLock.d.ts +3 -0
  163. package/dist/useScrollLock.d.ts.map +1 -0
  164. package/dist/useScrollLock.js +53 -0
  165. package/dist/useScrollLock.js.map +1 -0
  166. package/package.json +57 -6
  167. package/src/AuiIf.ts +34 -0
  168. package/src/AuiProvider.ts +90 -0
  169. package/src/__tests__/AuiIf.test.ts +54 -0
  170. package/src/__tests__/AuiProvider.test.ts +266 -0
  171. package/src/__tests__/byindex-shrink-race.test.ts +411 -0
  172. package/src/__tests__/dist-graph.test.ts +49 -0
  173. package/src/__tests__/fixtures.ts +51 -0
  174. package/src/__tests__/primitives-actionbar.test.ts +469 -0
  175. package/src/__tests__/primitives-attachment.test.ts +493 -0
  176. package/src/__tests__/primitives-content.test.ts +749 -0
  177. package/src/__tests__/primitives-keyed-items.test.ts +270 -0
  178. package/src/__tests__/primitives-keyed-messages.test.ts +264 -0
  179. package/src/__tests__/primitives-structural.test.ts +573 -0
  180. package/src/__tests__/primitives-suggestions.test.ts +374 -0
  181. package/src/__tests__/primitives-threadlist.test.ts +443 -0
  182. package/src/__tests__/primitives-tool-ui.test.ts +579 -0
  183. package/src/__tests__/primitives.test.ts +361 -0
  184. package/src/__tests__/useAuiEvent.test.ts +159 -0
  185. package/src/__tests__/useAuiState.test.ts +124 -0
  186. package/src/context.ts +31 -0
  187. package/src/index.ts +87 -0
  188. package/src/primitives/AttachmentByIndexProvider.ts +88 -0
  189. package/src/primitives/ChainOfThoughtPrimitiveAccordionTrigger.test.ts +105 -0
  190. package/src/primitives/ChainOfThoughtPrimitiveAccordionTrigger.ts +38 -0
  191. package/src/primitives/ChainOfThoughtPrimitiveParts.test.ts +254 -0
  192. package/src/primitives/ChainOfThoughtPrimitiveParts.ts +112 -0
  193. package/src/primitives/ComposerPrimitiveCancel.ts +41 -0
  194. package/src/primitives/ComposerPrimitiveInput.ts +64 -0
  195. package/src/primitives/ComposerPrimitiveSend.ts +39 -0
  196. package/src/primitives/MessageByIdProvider.ts +89 -0
  197. package/src/primitives/MessagePrimitiveParts.tool-interaction.test.ts +88 -0
  198. package/src/primitives/MessagePrimitiveParts.ts +166 -0
  199. package/src/primitives/PartByIndexProvider.ts +62 -0
  200. package/src/primitives/ThreadListItemPrimitiveRoot.ts +57 -0
  201. package/src/primitives/ThreadListItemPrimitiveTrigger.ts +74 -0
  202. package/src/primitives/ThreadListKeyboardNav.test.ts +298 -0
  203. package/src/primitives/ThreadListPrimitiveRoot.ts +18 -0
  204. package/src/primitives/ThreadPrimitiveMessages.ts +40 -0
  205. package/src/primitives/ThreadPrimitiveScrollToBottom.ts +65 -0
  206. package/src/primitives/ThreadPrimitiveViewport.ts +233 -0
  207. package/src/primitives/ThreadPrimitiveViewportFooter.test.ts +305 -0
  208. package/src/primitives/ThreadPrimitiveViewportFooter.ts +60 -0
  209. package/src/primitives/actionBar.ts +159 -0
  210. package/src/primitives/attachment.ts +76 -0
  211. package/src/primitives/attrDisabled.ts +6 -0
  212. package/src/primitives/branchPicker.ts +77 -0
  213. package/src/primitives/composerAttachments.ts +181 -0
  214. package/src/primitives/error.ts +36 -0
  215. package/src/primitives/lastValidCache.ts +12 -0
  216. package/src/primitives/message.ts +54 -0
  217. package/src/primitives/messageAttachments.ts +32 -0
  218. package/src/primitives/stableKeys.test.ts +19 -0
  219. package/src/primitives/stableKeys.ts +21 -0
  220. package/src/primitives/suggestions.ts +179 -0
  221. package/src/primitives/thread.ts +46 -0
  222. package/src/primitives/threadList.ts +163 -0
  223. package/src/primitives/threadListFocusGroup.ts +56 -0
  224. package/src/primitives/threadListStructural.ts +86 -0
  225. package/src/primitives/useComposerSendState.ts +13 -0
  226. package/src/primitives/viewportContext.ts +16 -0
  227. package/src/useAui.ts +18 -0
  228. package/src/useAuiEvent.ts +64 -0
  229. package/src/useAuiState.ts +55 -0
  230. package/src/useScrollLock.test.ts +207 -0
  231. package/src/useScrollLock.ts +79 -0
@@ -0,0 +1,411 @@
1
+ import { afterEach, describe, expect, it, type Mock, vi } from "vitest";
2
+ import { createApp, defineComponent, h, nextTick } from "vue";
3
+ import { flushTapSync } from "@assistant-ui/tap";
4
+ import { AuiConfig } from "@assistant-ui/store/client";
5
+ import { RuntimeAdapter } from "@assistant-ui/core/store";
6
+ import type { ExternalStoreAdapter } from "@assistant-ui/core";
7
+ import {
8
+ AssistantRuntimeImpl,
9
+ ExternalStoreRuntimeCore,
10
+ } from "@assistant-ui/core/internal";
11
+ import { AuiProvider } from "../AuiProvider";
12
+ import { useAuiState } from "../useAuiState";
13
+ import { ThreadPrimitiveMessages } from "../primitives/ThreadPrimitiveMessages";
14
+ import { MessagePrimitiveParts } from "../primitives/MessagePrimitiveParts";
15
+ import { PartByIndexProvider } from "../primitives/PartByIndexProvider";
16
+ import { ActionBarPrimitiveReload } from "../primitives/actionBar";
17
+
18
+ type DemoMessage = { id: string; role: "user" | "assistant"; text: string };
19
+
20
+ const convertDemoMessage = (message: DemoMessage) => ({
21
+ id: message.id,
22
+ role: message.role,
23
+ content:
24
+ message.text === "" ? [] : [{ type: "text" as const, text: message.text }],
25
+ });
26
+
27
+ let nextId = 0;
28
+ const freshId = (prefix: string) => `${prefix}-${nextId++}`;
29
+
30
+ const createShrinkRuntime = () => {
31
+ let messages: readonly DemoMessage[] = [];
32
+ let isRunning = false;
33
+ const makeAdapter = (): ExternalStoreAdapter<DemoMessage> => ({
34
+ messages,
35
+ isRunning,
36
+ convertMessage: convertDemoMessage,
37
+ onNew: async () => {},
38
+ setMessages: (next) => {
39
+ messages = next;
40
+ sync();
41
+ },
42
+ onReload: async () => {
43
+ messages = [
44
+ ...messages.slice(0, -1),
45
+ { id: freshId("assistant"), role: "assistant", text: "" },
46
+ ];
47
+ isRunning = true;
48
+ sync();
49
+ },
50
+ });
51
+ const core = new ExternalStoreRuntimeCore(makeAdapter());
52
+ const runtime = new AssistantRuntimeImpl(core);
53
+ const sync = () => core.setAdapter(makeAdapter());
54
+ const seed = (next: readonly DemoMessage[], running = false) => {
55
+ messages = next;
56
+ isRunning = running;
57
+ sync();
58
+ };
59
+ return { runtime, seed };
60
+ };
61
+
62
+ const PartsView = defineComponent({
63
+ setup() {
64
+ const role = useAuiState((s) => s.message.role);
65
+ return () =>
66
+ h("li", { class: "msg", "data-role": role.value }, [
67
+ h(MessagePrimitiveParts),
68
+ h(
69
+ ActionBarPrimitiveReload,
70
+ { class: "reload" },
71
+ { default: () => "Reload" },
72
+ ),
73
+ ]);
74
+ },
75
+ });
76
+
77
+ const mountChat = (runtime: AssistantRuntimeImpl) => {
78
+ const app = createApp(
79
+ defineComponent({
80
+ setup: () => () =>
81
+ h(
82
+ AuiProvider,
83
+ { config: AuiConfig({ threads: RuntimeAdapter(runtime) }) },
84
+ {
85
+ default: () =>
86
+ h(ThreadPrimitiveMessages, null, {
87
+ default: () => h(PartsView),
88
+ }),
89
+ },
90
+ ),
91
+ }),
92
+ );
93
+ const el = document.createElement("div");
94
+ app.mount(el);
95
+ return { el, unmount: () => app.unmount() };
96
+ };
97
+
98
+ const lookupErrors = (spy: Mock<typeof console.error>) =>
99
+ spy.mock.calls.filter((args) =>
100
+ args.some((arg) => String(arg).includes("useClientLookup")),
101
+ );
102
+
103
+ const staleReports = (spy: Mock<typeof console.error>) =>
104
+ spy.mock.calls.filter((args) =>
105
+ args.some((arg) =>
106
+ String(arg).includes(
107
+ "PartByIndexProvider: index 0 is still out of bounds",
108
+ ),
109
+ ),
110
+ );
111
+
112
+ const PartTypeReader = defineComponent({
113
+ setup() {
114
+ const type = useAuiState((s) => s.part.type);
115
+ return () => h("span", { class: "part-type" }, type.value);
116
+ },
117
+ });
118
+
119
+ const PinnedPartView = defineComponent({
120
+ setup() {
121
+ const role = useAuiState((s) => s.message.role);
122
+ return () =>
123
+ h("li", { class: "msg", "data-role": role.value }, [
124
+ h(
125
+ PartByIndexProvider,
126
+ { index: 0 },
127
+ { default: () => h(PartTypeReader) },
128
+ ),
129
+ ]);
130
+ },
131
+ });
132
+
133
+ const mountPinnedPart = (runtime: AssistantRuntimeImpl) => {
134
+ const app = createApp(
135
+ defineComponent({
136
+ setup: () => () =>
137
+ h(
138
+ AuiProvider,
139
+ { config: AuiConfig({ threads: RuntimeAdapter(runtime) }) },
140
+ {
141
+ default: () =>
142
+ h(ThreadPrimitiveMessages, null, {
143
+ default: () => h(PinnedPartView),
144
+ }),
145
+ },
146
+ ),
147
+ }),
148
+ );
149
+ const el = document.createElement("div");
150
+ app.mount(el);
151
+ return { el, app };
152
+ };
153
+
154
+ describe("by-index scope shrink races", () => {
155
+ afterEach(() => {
156
+ vi.restoreAllMocks();
157
+ });
158
+
159
+ it("reload replacing the assistant message with empty content logs no lookup error", async () => {
160
+ const { runtime, seed } = createShrinkRuntime();
161
+ const { el, unmount } = mountChat(runtime);
162
+
163
+ flushTapSync(() =>
164
+ seed([
165
+ { id: "u1", role: "user", text: "hi" },
166
+ { id: "a1", role: "assistant", text: "hello" },
167
+ ]),
168
+ );
169
+ await vi.waitFor(async () => {
170
+ await nextTick();
171
+ expect(el.querySelectorAll("li.msg")).toHaveLength(2);
172
+ expect(el.textContent).toContain("hello");
173
+ });
174
+
175
+ const errorSpy = vi.spyOn(console, "error");
176
+ el.querySelector<HTMLButtonElement>(
177
+ 'li[data-role="assistant"] button.reload',
178
+ )!.click();
179
+ await vi.waitFor(async () => {
180
+ await nextTick();
181
+ expect(el.textContent).not.toContain("hello");
182
+ });
183
+
184
+ expect(lookupErrors(errorSpy)).toEqual([]);
185
+
186
+ flushTapSync(() =>
187
+ seed([
188
+ { id: "u1", role: "user", text: "hi" },
189
+ { id: "a2", role: "assistant", text: "recovered" },
190
+ ]),
191
+ );
192
+ await vi.waitFor(async () => {
193
+ await nextTick();
194
+ expect(el.textContent).toContain("recovered");
195
+ });
196
+ expect(lookupErrors(errorSpy)).toEqual([]);
197
+
198
+ unmount();
199
+ });
200
+
201
+ it("shrinking the message list under mounted message scopes logs no lookup error", async () => {
202
+ const { runtime, seed } = createShrinkRuntime();
203
+ const { el, unmount } = mountChat(runtime);
204
+
205
+ flushTapSync(() =>
206
+ seed([
207
+ { id: "u1", role: "user", text: "one" },
208
+ { id: "a1", role: "assistant", text: "two" },
209
+ { id: "u2", role: "user", text: "three" },
210
+ { id: "a2", role: "assistant", text: "four" },
211
+ ]),
212
+ );
213
+ await vi.waitFor(async () => {
214
+ await nextTick();
215
+ expect(el.querySelectorAll("li.msg")).toHaveLength(4);
216
+ });
217
+
218
+ const errorSpy = vi.spyOn(console, "error");
219
+ flushTapSync(() =>
220
+ seed([
221
+ { id: "u1", role: "user", text: "one" },
222
+ { id: "a1", role: "assistant", text: "two" },
223
+ ]),
224
+ );
225
+ await vi.waitFor(async () => {
226
+ await nextTick();
227
+ expect(el.querySelectorAll("li.msg")).toHaveLength(2);
228
+ });
229
+
230
+ expect(lookupErrors(errorSpy)).toEqual([]);
231
+
232
+ unmount();
233
+ });
234
+
235
+ it("a provider still out of bounds after the flush reverts to throwing", async () => {
236
+ const { runtime, seed } = createShrinkRuntime();
237
+ const { el, app } = mountPinnedPart(runtime);
238
+
239
+ flushTapSync(() =>
240
+ seed([
241
+ { id: "u1", role: "user", text: "hi" },
242
+ { id: "a1", role: "assistant", text: "hello" },
243
+ ]),
244
+ );
245
+ await vi.waitFor(async () => {
246
+ await nextTick();
247
+ expect(el.querySelectorAll("span.part-type")).toHaveLength(2);
248
+ });
249
+
250
+ const errorSpy = vi.spyOn(console, "error");
251
+ flushTapSync(() =>
252
+ seed([
253
+ { id: "u1", role: "user", text: "hi" },
254
+ { id: "a1", role: "assistant", text: "" },
255
+ ]),
256
+ );
257
+ expect(lookupErrors(errorSpy)).toEqual([]);
258
+
259
+ await vi.waitFor(async () => {
260
+ await nextTick();
261
+ expect(staleReports(errorSpy).length).toBeGreaterThan(0);
262
+ });
263
+
264
+ app.unmount();
265
+ });
266
+
267
+ it("a same-tick recovery keeps the refreshed cache and a later shrink still expires", async () => {
268
+ const { runtime, seed } = createShrinkRuntime();
269
+ const { el, app } = mountPinnedPart(runtime);
270
+
271
+ flushTapSync(() =>
272
+ seed([
273
+ { id: "u1", role: "user", text: "hi" },
274
+ { id: "a1", role: "assistant", text: "hello" },
275
+ ]),
276
+ );
277
+ await vi.waitFor(async () => {
278
+ await nextTick();
279
+ expect(el.querySelectorAll("span.part-type")).toHaveLength(2);
280
+ });
281
+
282
+ const errorSpy = vi.spyOn(console, "error");
283
+ flushTapSync(() =>
284
+ seed([
285
+ { id: "u1", role: "user", text: "hi" },
286
+ { id: "a1", role: "assistant", text: "" },
287
+ ]),
288
+ );
289
+ flushTapSync(() =>
290
+ seed([
291
+ { id: "u1", role: "user", text: "hi" },
292
+ { id: "a1", role: "assistant", text: "restored" },
293
+ ]),
294
+ );
295
+ flushTapSync(() =>
296
+ seed([
297
+ { id: "u1", role: "user", text: "hi" },
298
+ { id: "a1", role: "assistant", text: "" },
299
+ ]),
300
+ );
301
+
302
+ await vi.waitFor(async () => {
303
+ await nextTick();
304
+ expect(staleReports(errorSpy).length).toBeGreaterThan(0);
305
+ });
306
+ expect(lookupErrors(errorSpy)).toEqual([]);
307
+
308
+ const reportsBeforeRecovery = staleReports(errorSpy).length;
309
+ flushTapSync(() =>
310
+ seed([
311
+ { id: "u1", role: "user", text: "hi" },
312
+ { id: "a1", role: "assistant", text: "again" },
313
+ ]),
314
+ );
315
+ await vi.waitFor(async () => {
316
+ await nextTick();
317
+ expect(el.querySelectorAll("span.part-type")).toHaveLength(2);
318
+ });
319
+ expect(staleReports(errorSpy).length).toBe(reportsBeforeRecovery);
320
+ expect(lookupErrors(errorSpy)).toEqual([]);
321
+
322
+ app.unmount();
323
+ });
324
+
325
+ it("a recovered cache survives the expiry and still guards a later shrink", async () => {
326
+ const { runtime, seed } = createShrinkRuntime();
327
+ const { el, app } = mountPinnedPart(runtime);
328
+
329
+ flushTapSync(() =>
330
+ seed([
331
+ { id: "u1", role: "user", text: "hi" },
332
+ { id: "a1", role: "assistant", text: "hello" },
333
+ ]),
334
+ );
335
+ await vi.waitFor(async () => {
336
+ await nextTick();
337
+ expect(el.querySelectorAll("span.part-type")).toHaveLength(2);
338
+ });
339
+
340
+ const errorSpy = vi.spyOn(console, "error");
341
+ flushTapSync(() =>
342
+ seed([
343
+ { id: "u1", role: "user", text: "hi" },
344
+ { id: "a1", role: "assistant", text: "" },
345
+ ]),
346
+ );
347
+ flushTapSync(() =>
348
+ seed([
349
+ { id: "u1", role: "user", text: "hi" },
350
+ { id: "a1", role: "assistant", text: "restored" },
351
+ ]),
352
+ );
353
+ await vi.waitFor(async () => {
354
+ await nextTick();
355
+ expect(el.querySelectorAll("span.part-type")).toHaveLength(2);
356
+ });
357
+ expect(staleReports(errorSpy)).toEqual([]);
358
+
359
+ flushTapSync(() =>
360
+ seed([
361
+ { id: "u1", role: "user", text: "hi" },
362
+ { id: "a1", role: "assistant", text: "" },
363
+ ]),
364
+ );
365
+ await vi.waitFor(async () => {
366
+ await nextTick();
367
+ expect(staleReports(errorSpy).length).toBeGreaterThan(0);
368
+ });
369
+ expect(lookupErrors(errorSpy)).toEqual([]);
370
+
371
+ app.unmount();
372
+ });
373
+
374
+ it("a never-valid part index still throws when read", () => {
375
+ const { runtime, seed } = createShrinkRuntime();
376
+ seed([
377
+ { id: "u1", role: "user", text: "hi" },
378
+ { id: "a1", role: "assistant", text: "hello" },
379
+ ]);
380
+
381
+ const NeverValidReader = defineComponent({
382
+ setup() {
383
+ const type = useAuiState((s) => s.part.type);
384
+ return () => h("span", type.value);
385
+ },
386
+ });
387
+
388
+ const app = createApp(
389
+ defineComponent({
390
+ setup: () => () =>
391
+ h(
392
+ AuiProvider,
393
+ { config: AuiConfig({ threads: RuntimeAdapter(runtime) }) },
394
+ {
395
+ default: () =>
396
+ h(ThreadPrimitiveMessages, null, {
397
+ default: () =>
398
+ h(
399
+ PartByIndexProvider,
400
+ { index: 99 },
401
+ { default: () => h(NeverValidReader) },
402
+ ),
403
+ }),
404
+ },
405
+ ),
406
+ }),
407
+ );
408
+ const el = document.createElement("div");
409
+ expect(() => app.mount(el)).toThrow(/out of bounds/);
410
+ });
411
+ });
@@ -0,0 +1,49 @@
1
+ /// <reference types="node" />
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const DIST_DIR = resolve(__dirname, "../../dist");
7
+ const DIST_ENTRY = resolve(DIST_DIR, "index.js");
8
+
9
+ describe("@assistant-ui/vue dist graph", () => {
10
+ it.skipIf(!existsSync(DIST_ENTRY))(
11
+ "contains no react, react-shim, or compiler-runtime references",
12
+ () => {
13
+ const visited = new Set<string>();
14
+ const pending = [DIST_ENTRY];
15
+ const violations: string[] = [];
16
+
17
+ while (pending.length > 0) {
18
+ const file = pending.pop()!;
19
+ if (visited.has(file)) continue;
20
+ visited.add(file);
21
+
22
+ const source = readFileSync(file, "utf8");
23
+ for (const match of source.matchAll(
24
+ /(?:from\s+|import\s+|import\s*\(\s*)["']([^"']+)["']/g,
25
+ )) {
26
+ const specifier = match[1]!;
27
+ if (
28
+ specifier === "react" ||
29
+ specifier.startsWith("react/") ||
30
+ specifier.includes("react-shim") ||
31
+ specifier.includes("compiler-runtime")
32
+ ) {
33
+ violations.push(`${relative(DIST_DIR, file)} -> ${specifier}`);
34
+ continue;
35
+ }
36
+ if (specifier.startsWith(".")) {
37
+ pending.push(resolve(dirname(file), specifier));
38
+ }
39
+ }
40
+ }
41
+
42
+ expect(
43
+ violations,
44
+ `The vue bridge must build without the react compiler: aui-build's compiler gate requires a react peer, and this package must never declare one. Violations:\n` +
45
+ violations.map((v) => ` - ${v}`).join("\n"),
46
+ ).toEqual([]);
47
+ },
48
+ );
49
+ });
@@ -0,0 +1,51 @@
1
+ import { useEffect, useState } from "react";
2
+ import { resource } from "@assistant-ui/tap";
3
+ import {
4
+ Derived,
5
+ useAssistantEmit,
6
+ useClientResource,
7
+ } from "@assistant-ui/store/client";
8
+
9
+ export type AnyClient = Record<string, any>;
10
+
11
+ export const flushEvents = () => new Promise((resolve) => setTimeout(resolve));
12
+
13
+ export const trackers = { threadCleanups: 0 };
14
+
15
+ const useMessageClient = ({ id }: { id: string }) => {
16
+ const emit = useAssistantEmit();
17
+ const [text, setText] = useState("");
18
+ return {
19
+ getState: () => ({ id, text }),
20
+ setText,
21
+ ping: (value: string) =>
22
+ emit("message.pinged" as never, { id, value } as never),
23
+ };
24
+ };
25
+ export const MessageClient = resource(useMessageClient);
26
+
27
+ const useThreadClient = () => {
28
+ const [selected, setSelected] = useState(0);
29
+ const m0 = useClientResource(MessageClient({ id: "m0" }));
30
+ const m1 = useClientResource(MessageClient({ id: "m1" }));
31
+ const messages = [m0, m1];
32
+ useEffect(() => {
33
+ return () => {
34
+ trackers.threadCleanups++;
35
+ };
36
+ }, []);
37
+ return {
38
+ getState: () => ({ selected }),
39
+ setSelected,
40
+ message: ({ index }: { index: number }) => messages[index]!.methods,
41
+ };
42
+ };
43
+ export const ThreadClient = resource(useThreadClient);
44
+
45
+ export const messageDerived = () =>
46
+ Derived({
47
+ source: "thread",
48
+ query: {},
49
+ get: (aui: AnyClient) =>
50
+ aui.thread.message({ index: aui.thread.getState().selected }),
51
+ } as never);