@assistant-ui/vue 0.0.0-stage → 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 +64 -3
  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,443 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { createApp, defineComponent, h, nextTick, type Component } from "vue";
3
+ import { AuiConfig } from "@assistant-ui/store/client";
4
+ import { RuntimeAdapter } from "@assistant-ui/core/store";
5
+ import type { ExternalStoreAdapter } from "@assistant-ui/core";
6
+ import {
7
+ AssistantRuntimeImpl,
8
+ ExternalStoreRuntimeCore,
9
+ } from "@assistant-ui/core/internal";
10
+ import { AuiProvider } from "../AuiProvider";
11
+ import { ThreadPrimitiveMessages } from "../primitives/ThreadPrimitiveMessages";
12
+ import { ThreadPrimitiveViewport } from "../primitives/ThreadPrimitiveViewport";
13
+ import {
14
+ ThreadListItemPrimitiveTitle,
15
+ ThreadListItemPrimitiveTrigger,
16
+ ThreadListPrimitiveItems,
17
+ ThreadListPrimitiveNew,
18
+ } from "../primitives/threadList";
19
+ import { useAuiState } from "../useAuiState";
20
+
21
+ type DemoMessage = { id: string; role: "user" | "assistant"; text: string };
22
+ type DemoThread = { id: string; title: string; messages: DemoMessage[] };
23
+
24
+ const createMultiThreadRuntime = () => {
25
+ let threads: DemoThread[] = [
26
+ {
27
+ id: "t1",
28
+ title: "First thread",
29
+ messages: [{ id: "m1", role: "user", text: "hello from one" }],
30
+ },
31
+ {
32
+ id: "t2",
33
+ title: "Second thread",
34
+ messages: [{ id: "m2", role: "user", text: "hello from two" }],
35
+ },
36
+ ];
37
+ let currentId = "t1";
38
+ let nextThread = 3;
39
+ const onSwitchToThread = vi.fn((threadId: string) => {
40
+ currentId = threadId;
41
+ sync();
42
+ });
43
+ const onSwitchToNewThread = vi.fn(() => {
44
+ const id = `t${nextThread++}`;
45
+ threads = [...threads, { id, title: "", messages: [] }];
46
+ currentId = id;
47
+ sync();
48
+ });
49
+ const makeAdapter = (): ExternalStoreAdapter<DemoMessage> => ({
50
+ messages: threads.find((thread) => thread.id === currentId)!.messages,
51
+ convertMessage: (message) => ({
52
+ id: message.id,
53
+ role: message.role,
54
+ content: [{ type: "text", text: message.text }],
55
+ }),
56
+ onNew: async () => {},
57
+ adapters: {
58
+ threadList: {
59
+ threadId: currentId,
60
+ threads: threads.map((thread) => ({
61
+ status: "regular" as const,
62
+ id: thread.id,
63
+ title: thread.title,
64
+ })),
65
+ archivedThreads: [
66
+ { status: "archived" as const, id: "ta", title: "Archived one" },
67
+ ],
68
+ onSwitchToThread,
69
+ onSwitchToNewThread,
70
+ },
71
+ },
72
+ });
73
+ const core = new ExternalStoreRuntimeCore(makeAdapter());
74
+ const runtime = new AssistantRuntimeImpl(core);
75
+ const sync = () => core.setAdapter(makeAdapter());
76
+ const removeThread = (threadId: string) => {
77
+ threads = threads.filter((thread) => thread.id !== threadId);
78
+ if (!threads.some((thread) => thread.id === currentId)) {
79
+ currentId = threads[0]!.id;
80
+ }
81
+ sync();
82
+ };
83
+ return { runtime, onSwitchToThread, onSwitchToNewThread, removeThread };
84
+ };
85
+
86
+ const SidebarView = defineComponent({
87
+ setup: () => () => [
88
+ h(ThreadListPrimitiveNew, { class: "new" }, { default: () => "New" }),
89
+ h(ThreadListPrimitiveItems, null, {
90
+ default: () =>
91
+ h(
92
+ ThreadListItemPrimitiveTrigger,
93
+ { class: "item" },
94
+ {
95
+ default: () =>
96
+ h(ThreadListItemPrimitiveTitle, { fallback: "New Chat" }),
97
+ },
98
+ ),
99
+ }),
100
+ ],
101
+ });
102
+
103
+ const mountView = (runtime: AssistantRuntimeImpl, view: Component) => {
104
+ const app = createApp(
105
+ defineComponent({
106
+ setup: () => () =>
107
+ h(
108
+ AuiProvider,
109
+ { config: AuiConfig({ threads: RuntimeAdapter(runtime) }) },
110
+ { default: () => h(view) },
111
+ ),
112
+ }),
113
+ );
114
+ const el = document.createElement("div");
115
+ app.mount(el);
116
+ return { el, unmount: () => app.unmount() };
117
+ };
118
+
119
+ describe("thread list primitives", () => {
120
+ it("renders one scoped item per thread with titles", async () => {
121
+ const { runtime } = createMultiThreadRuntime();
122
+ const { el, unmount } = mountView(runtime, SidebarView);
123
+
124
+ await vi.waitFor(async () => {
125
+ await nextTick();
126
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
127
+ });
128
+ expect(
129
+ [...el.querySelectorAll("button.item")].map((item) => item.textContent),
130
+ ).toEqual(["First thread", "Second thread"]);
131
+
132
+ unmount();
133
+ });
134
+
135
+ it("keeps component state with a thread after an earlier row is removed", async () => {
136
+ const { runtime, removeThread } = createMultiThreadRuntime();
137
+ const useStatefulItem = () => {
138
+ const initialTitle = useAuiState((s) => s.threadListItem.title).value;
139
+ return () => h("span", { class: "stateful-item" }, initialTitle);
140
+ };
141
+ const StatefulItem = defineComponent({
142
+ setup: useStatefulItem,
143
+ });
144
+ const View = defineComponent({
145
+ setup: () => () =>
146
+ h(ThreadListPrimitiveItems, null, {
147
+ default: () => h(StatefulItem),
148
+ }),
149
+ });
150
+ const { el, unmount } = mountView(runtime, View);
151
+
152
+ await vi.waitFor(async () => {
153
+ await nextTick();
154
+ expect(el.querySelectorAll(".stateful-item")).toHaveLength(2);
155
+ });
156
+
157
+ removeThread("t1");
158
+ await vi.waitFor(async () => {
159
+ await nextTick();
160
+ expect(el.querySelectorAll(".stateful-item")).toHaveLength(1);
161
+ });
162
+ expect(el.querySelector(".stateful-item")!.textContent).toBe(
163
+ "Second thread",
164
+ );
165
+
166
+ unmount();
167
+ });
168
+
169
+ it("switches threads through the item trigger and swaps the thread state", async () => {
170
+ const { runtime, onSwitchToThread } = createMultiThreadRuntime();
171
+ const View = defineComponent({
172
+ setup: () => () => [
173
+ h(SidebarView),
174
+ h("ol", null, [
175
+ h(ThreadPrimitiveMessages, null, {
176
+ default: () => h("li", "row"),
177
+ }),
178
+ ]),
179
+ ],
180
+ });
181
+ const { el, unmount } = mountView(runtime, View);
182
+
183
+ await vi.waitFor(async () => {
184
+ await nextTick();
185
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
186
+ });
187
+
188
+ expect(
189
+ el
190
+ .querySelectorAll<HTMLButtonElement>("button.item")[0]!
191
+ .getAttribute("data-active"),
192
+ ).toBe("true");
193
+ expect(
194
+ el
195
+ .querySelectorAll<HTMLButtonElement>("button.item")[1]!
196
+ .hasAttribute("data-active"),
197
+ ).toBe(false);
198
+
199
+ el.querySelectorAll<HTMLButtonElement>("button.item")[1]!.click();
200
+ await vi.waitFor(() => {
201
+ expect(onSwitchToThread).toHaveBeenCalledWith("t2");
202
+ });
203
+ await vi.waitFor(async () => {
204
+ await nextTick();
205
+ expect(
206
+ el
207
+ .querySelectorAll<HTMLButtonElement>("button.item")[1]!
208
+ .getAttribute("aria-current"),
209
+ ).toBe("true");
210
+ });
211
+ expect(
212
+ el
213
+ .querySelectorAll<HTMLButtonElement>("button.item")[0]!
214
+ .hasAttribute("data-active"),
215
+ ).toBe(false);
216
+ await vi.waitFor(() => {
217
+ expect(runtime.thread.getState().messages[0]!.content[0]).toMatchObject({
218
+ type: "text",
219
+ text: "hello from two",
220
+ });
221
+ });
222
+
223
+ unmount();
224
+ });
225
+
226
+ it("starts a new thread through the new button", async () => {
227
+ const { runtime, onSwitchToNewThread } = createMultiThreadRuntime();
228
+ const { el, unmount } = mountView(runtime, SidebarView);
229
+
230
+ await vi.waitFor(async () => {
231
+ await nextTick();
232
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
233
+ });
234
+
235
+ el.querySelector<HTMLButtonElement>("button.new")!.click();
236
+ await vi.waitFor(() => {
237
+ expect(onSwitchToNewThread).toHaveBeenCalledTimes(1);
238
+ });
239
+ await vi.waitFor(async () => {
240
+ await nextTick();
241
+ expect(el.querySelectorAll("button.item")).toHaveLength(3);
242
+ });
243
+ expect(
244
+ el.querySelectorAll<HTMLButtonElement>("button.item")[2]!.textContent,
245
+ ).toBe("New Chat");
246
+
247
+ unmount();
248
+ });
249
+
250
+ it("renders archived items separately with archived", async () => {
251
+ const { runtime } = createMultiThreadRuntime();
252
+ const View = defineComponent({
253
+ setup: () => () => [
254
+ h(SidebarView),
255
+ h(
256
+ ThreadListPrimitiveItems,
257
+ { archived: true },
258
+ {
259
+ default: () =>
260
+ h(
261
+ ThreadListItemPrimitiveTrigger,
262
+ { class: "arch" },
263
+ { default: () => h(ThreadListItemPrimitiveTitle) },
264
+ ),
265
+ },
266
+ ),
267
+ ],
268
+ });
269
+ const { el, unmount } = mountView(runtime, View);
270
+
271
+ await vi.waitFor(async () => {
272
+ await nextTick();
273
+ expect(el.querySelectorAll("button.arch")).toHaveLength(1);
274
+ });
275
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
276
+ expect(el.querySelector("button.arch")!.textContent).toBe("Archived one");
277
+
278
+ unmount();
279
+ });
280
+
281
+ it("ignores clicks while disabled or default-prevented", async () => {
282
+ const { runtime, onSwitchToThread, onSwitchToNewThread } =
283
+ createMultiThreadRuntime();
284
+ const View = defineComponent({
285
+ setup: () => () => [
286
+ h(
287
+ ThreadListPrimitiveNew,
288
+ { class: "new", disabled: true },
289
+ { default: () => "New" },
290
+ ),
291
+ h(ThreadListPrimitiveItems, null, {
292
+ default: () =>
293
+ h(ThreadListItemPrimitiveTrigger, {
294
+ class: "item",
295
+ onClick: (event: MouseEvent) => event.preventDefault(),
296
+ }),
297
+ }),
298
+ ],
299
+ });
300
+ const { el, unmount } = mountView(runtime, View);
301
+
302
+ await vi.waitFor(async () => {
303
+ await nextTick();
304
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
305
+ });
306
+ const newButton = el.querySelector<HTMLButtonElement>("button.new")!;
307
+ expect(newButton.disabled).toBe(true);
308
+
309
+ newButton.disabled = false;
310
+ newButton.click();
311
+ el.querySelectorAll<HTMLButtonElement>("button.item")[1]!.click();
312
+ await nextTick();
313
+ await Promise.resolve();
314
+ expect(onSwitchToNewThread).not.toHaveBeenCalled();
315
+ expect(onSwitchToThread).not.toHaveBeenCalled();
316
+
317
+ unmount();
318
+ });
319
+
320
+ it("ignores item trigger clicks while disabled", async () => {
321
+ const { runtime, onSwitchToThread } = createMultiThreadRuntime();
322
+ const View = defineComponent({
323
+ setup: () => () =>
324
+ h(ThreadListPrimitiveItems, null, {
325
+ default: () =>
326
+ h(ThreadListItemPrimitiveTrigger, {
327
+ class: "item",
328
+ disabled: true,
329
+ }),
330
+ }),
331
+ });
332
+ const { el, unmount } = mountView(runtime, View);
333
+
334
+ await vi.waitFor(async () => {
335
+ await nextTick();
336
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
337
+ });
338
+
339
+ const trigger = el.querySelectorAll<HTMLButtonElement>("button.item")[1]!;
340
+ expect(trigger.disabled).toBe(true);
341
+ trigger.disabled = false;
342
+ trigger.click();
343
+ await nextTick();
344
+ await Promise.resolve();
345
+ expect(onSwitchToThread).not.toHaveBeenCalled();
346
+
347
+ unmount();
348
+ });
349
+
350
+ it("renders the default slot as the title fallback", async () => {
351
+ const { runtime } = createMultiThreadRuntime();
352
+ const View = defineComponent({
353
+ setup: () => () => [
354
+ h(ThreadListPrimitiveNew, { class: "new" }, { default: () => "New" }),
355
+ h(ThreadListPrimitiveItems, null, {
356
+ default: () =>
357
+ h(
358
+ ThreadListItemPrimitiveTrigger,
359
+ { class: "item" },
360
+ {
361
+ default: () =>
362
+ h(
363
+ ThreadListItemPrimitiveTitle,
364
+ { fallback: "unused" },
365
+ { default: () => h("em", "Untitled") },
366
+ ),
367
+ },
368
+ ),
369
+ }),
370
+ ],
371
+ });
372
+ const { el, unmount } = mountView(runtime, View);
373
+
374
+ await vi.waitFor(async () => {
375
+ await nextTick();
376
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
377
+ });
378
+ expect(
379
+ el.querySelectorAll<HTMLButtonElement>("button.item")[0]!.textContent,
380
+ ).toBe("First thread");
381
+
382
+ el.querySelector<HTMLButtonElement>("button.new")!.click();
383
+ await vi.waitFor(async () => {
384
+ await nextTick();
385
+ expect(el.querySelectorAll("button.item")).toHaveLength(3);
386
+ });
387
+ expect(
388
+ el
389
+ .querySelectorAll<HTMLButtonElement>("button.item")[2]!
390
+ .querySelector("em")!.textContent,
391
+ ).toBe("Untitled");
392
+
393
+ unmount();
394
+ });
395
+
396
+ it("scrolls the viewport to the bottom on thread switch", async () => {
397
+ const { runtime } = createMultiThreadRuntime();
398
+ const View = defineComponent({
399
+ setup: () => () => [
400
+ h(SidebarView),
401
+ h(
402
+ ThreadPrimitiveViewport,
403
+ { class: "viewport", scrollToBottomOnInitialize: false },
404
+ {
405
+ default: () =>
406
+ h(ThreadPrimitiveMessages, null, {
407
+ default: () => h("p", "row"),
408
+ }),
409
+ },
410
+ ),
411
+ ],
412
+ });
413
+ const { el, unmount } = mountView(runtime, View);
414
+
415
+ const div = el.querySelector<HTMLElement>("div.viewport")!;
416
+ Object.defineProperty(div, "scrollHeight", {
417
+ get: () => 500,
418
+ configurable: true,
419
+ });
420
+ Object.defineProperty(div, "clientHeight", {
421
+ get: () => 100,
422
+ configurable: true,
423
+ });
424
+ const scrollTo = vi.fn();
425
+ Object.defineProperty(div, "scrollTo", {
426
+ value: scrollTo,
427
+ configurable: true,
428
+ });
429
+
430
+ await vi.waitFor(async () => {
431
+ await nextTick();
432
+ expect(el.querySelectorAll("button.item")).toHaveLength(2);
433
+ });
434
+ expect(scrollTo).not.toHaveBeenCalled();
435
+
436
+ el.querySelectorAll<HTMLButtonElement>("button.item")[1]!.click();
437
+ await vi.waitFor(() => {
438
+ expect(scrollTo).toHaveBeenCalled();
439
+ });
440
+
441
+ unmount();
442
+ });
443
+ });