@agent-native/toolkit 0.21.3 → 0.22.0-nightly-20260926111339

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 (166) hide show
  1. package/agent-native.eject.json +11 -20
  2. package/dist/agentkit.d.ts +5 -1
  3. package/dist/agentkit.d.ts.map +1 -1
  4. package/dist/agentkit.js +2 -0
  5. package/dist/agentkit.js.map +1 -1
  6. package/dist/app-shell/PromptHome.d.ts +26 -0
  7. package/dist/app-shell/PromptHome.d.ts.map +1 -0
  8. package/dist/app-shell/PromptHome.js +19 -0
  9. package/dist/app-shell/PromptHome.js.map +1 -0
  10. package/dist/app-shell/PromptHome.spec.d.ts +2 -0
  11. package/dist/app-shell/PromptHome.spec.d.ts.map +1 -0
  12. package/dist/app-shell/PromptHome.spec.js +206 -0
  13. package/dist/app-shell/PromptHome.spec.js.map +1 -0
  14. package/dist/app-shell/TemplateLibraryGrid.d.ts +48 -0
  15. package/dist/app-shell/TemplateLibraryGrid.d.ts.map +1 -0
  16. package/dist/app-shell/TemplateLibraryGrid.js +20 -0
  17. package/dist/app-shell/TemplateLibraryGrid.js.map +1 -0
  18. package/dist/app-shell/TemplatePreviewDialog.d.ts +41 -0
  19. package/dist/app-shell/TemplatePreviewDialog.d.ts.map +1 -0
  20. package/dist/app-shell/TemplatePreviewDialog.js +50 -0
  21. package/dist/app-shell/TemplatePreviewDialog.js.map +1 -0
  22. package/dist/app-shell/TemplatePreviewDialog.spec.d.ts +2 -0
  23. package/dist/app-shell/TemplatePreviewDialog.spec.d.ts.map +1 -0
  24. package/dist/app-shell/TemplatePreviewDialog.spec.js +258 -0
  25. package/dist/app-shell/TemplatePreviewDialog.spec.js.map +1 -0
  26. package/dist/app-shell/index.d.ts +3 -0
  27. package/dist/app-shell/index.d.ts.map +1 -1
  28. package/dist/app-shell/index.js +3 -0
  29. package/dist/app-shell/index.js.map +1 -1
  30. package/dist/app-shell/prompt-home.css +159 -0
  31. package/dist/composer/ComposerContextMenu.d.ts +48 -0
  32. package/dist/composer/ComposerContextMenu.d.ts.map +1 -0
  33. package/dist/composer/ComposerContextMenu.js +295 -0
  34. package/dist/composer/ComposerContextMenu.js.map +1 -0
  35. package/dist/composer/ComposerContextMenu.spec.d.ts +2 -0
  36. package/dist/composer/ComposerContextMenu.spec.d.ts.map +1 -0
  37. package/dist/composer/ComposerContextMenu.spec.js +270 -0
  38. package/dist/composer/ComposerContextMenu.spec.js.map +1 -0
  39. package/dist/composer/ComposerContextMenuSearch.d.ts +11 -0
  40. package/dist/composer/ComposerContextMenuSearch.d.ts.map +1 -0
  41. package/dist/composer/ComposerContextMenuSearch.js +42 -0
  42. package/dist/composer/ComposerContextMenuSearch.js.map +1 -0
  43. package/dist/composer/ComposerContextPicker.d.ts +7 -0
  44. package/dist/composer/ComposerContextPicker.d.ts.map +1 -0
  45. package/dist/composer/ComposerContextPicker.js +71 -0
  46. package/dist/composer/ComposerContextPicker.js.map +1 -0
  47. package/dist/composer/ComposerContextPicker.spec.d.ts +2 -0
  48. package/dist/composer/ComposerContextPicker.spec.d.ts.map +1 -0
  49. package/dist/composer/ComposerContextPicker.spec.js +454 -0
  50. package/dist/composer/ComposerContextPicker.spec.js.map +1 -0
  51. package/dist/composer/ComposerContextPickerDialog.d.ts +8 -0
  52. package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -0
  53. package/dist/composer/ComposerContextPickerDialog.js +101 -0
  54. package/dist/composer/ComposerContextPickerDialog.js.map +1 -0
  55. package/dist/composer/ComposerContextPickerDialog.spec.d.ts +2 -0
  56. package/dist/composer/ComposerContextPickerDialog.spec.d.ts.map +1 -0
  57. package/dist/composer/ComposerContextPickerDialog.spec.js +329 -0
  58. package/dist/composer/ComposerContextPickerDialog.spec.js.map +1 -0
  59. package/dist/composer/ComposerContextSearchInput.d.ts +7 -0
  60. package/dist/composer/ComposerContextSearchInput.d.ts.map +1 -0
  61. package/dist/composer/ComposerContextSearchInput.js +18 -0
  62. package/dist/composer/ComposerContextSearchInput.js.map +1 -0
  63. package/dist/composer/PromptComposer.context.spec.d.ts +2 -0
  64. package/dist/composer/PromptComposer.context.spec.d.ts.map +1 -0
  65. package/dist/composer/PromptComposer.context.spec.js +383 -0
  66. package/dist/composer/PromptComposer.context.spec.js.map +1 -0
  67. package/dist/composer/PromptComposer.d.ts +19 -5
  68. package/dist/composer/PromptComposer.d.ts.map +1 -1
  69. package/dist/composer/PromptComposer.js +30 -25
  70. package/dist/composer/PromptComposer.js.map +1 -1
  71. package/dist/composer/PromptComposer.spec.js +24 -1
  72. package/dist/composer/PromptComposer.spec.js.map +1 -1
  73. package/dist/composer/TiptapComposer.d.ts +13 -2
  74. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  75. package/dist/composer/TiptapComposer.js +89 -32
  76. package/dist/composer/TiptapComposer.js.map +1 -1
  77. package/dist/composer/context-items.d.ts +12 -0
  78. package/dist/composer/context-items.d.ts.map +1 -0
  79. package/dist/composer/context-items.js +46 -0
  80. package/dist/composer/context-items.js.map +1 -0
  81. package/dist/composer/context-items.spec.d.ts +2 -0
  82. package/dist/composer/context-items.spec.d.ts.map +1 -0
  83. package/dist/composer/context-items.spec.js +43 -0
  84. package/dist/composer/context-items.spec.js.map +1 -0
  85. package/dist/composer/index.d.ts +4 -1
  86. package/dist/composer/index.d.ts.map +1 -1
  87. package/dist/composer/index.js +3 -1
  88. package/dist/composer/index.js.map +1 -1
  89. package/dist/composer/runtime-adapters.d.ts +6 -1
  90. package/dist/composer/runtime-adapters.d.ts.map +1 -1
  91. package/dist/composer/runtime-adapters.js.map +1 -1
  92. package/dist/composer/useComposerContextPicker.d.ts +111 -0
  93. package/dist/composer/useComposerContextPicker.d.ts.map +1 -0
  94. package/dist/composer/useComposerContextPicker.js +299 -0
  95. package/dist/composer/useComposerContextPicker.js.map +1 -0
  96. package/dist/design-system/default-adapter.d.ts.map +1 -1
  97. package/dist/design-system/default-adapter.js +13 -7
  98. package/dist/design-system/default-adapter.js.map +1 -1
  99. package/dist/design-system/default-adapter.spec.js +13 -0
  100. package/dist/design-system/default-adapter.spec.js.map +1 -1
  101. package/dist/design-system/types.d.ts +4 -1
  102. package/dist/design-system/types.d.ts.map +1 -1
  103. package/dist/index.d.ts +0 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +0 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/styles.css +1 -0
  108. package/dist/ui/command.d.ts +8 -2
  109. package/dist/ui/command.d.ts.map +1 -1
  110. package/dist/ui/command.js +9 -2
  111. package/dist/ui/command.js.map +1 -1
  112. package/dist/ui/command.spec.js +20 -1
  113. package/dist/ui/command.spec.js.map +1 -1
  114. package/package.json +2 -15
  115. package/src/agentkit.ts +26 -0
  116. package/src/app-shell/PromptHome.spec.tsx +404 -0
  117. package/src/app-shell/PromptHome.tsx +111 -0
  118. package/src/app-shell/TemplateLibraryGrid.tsx +212 -0
  119. package/src/app-shell/TemplatePreviewDialog.spec.tsx +473 -0
  120. package/src/app-shell/TemplatePreviewDialog.tsx +224 -0
  121. package/src/app-shell/index.ts +3 -0
  122. package/src/app-shell/prompt-home.css +159 -0
  123. package/src/composer/ComposerContextMenu.spec.tsx +343 -0
  124. package/src/composer/ComposerContextMenu.tsx +610 -0
  125. package/src/composer/ComposerContextMenuSearch.tsx +69 -0
  126. package/src/composer/ComposerContextPicker.spec.tsx +520 -0
  127. package/src/composer/ComposerContextPicker.tsx +280 -0
  128. package/src/composer/ComposerContextPickerDialog.spec.tsx +405 -0
  129. package/src/composer/ComposerContextPickerDialog.tsx +274 -0
  130. package/src/composer/ComposerContextSearchInput.tsx +48 -0
  131. package/src/composer/PromptComposer.context.spec.tsx +577 -0
  132. package/src/composer/PromptComposer.spec.ts +24 -1
  133. package/src/composer/PromptComposer.tsx +60 -26
  134. package/src/composer/TiptapComposer.tsx +198 -48
  135. package/src/composer/context-items.spec.ts +68 -0
  136. package/src/composer/context-items.ts +72 -0
  137. package/src/composer/index.ts +27 -0
  138. package/src/composer/runtime-adapters.tsx +6 -1
  139. package/src/composer/useComposerContextPicker.ts +441 -0
  140. package/src/design-system/default-adapter.spec.tsx +23 -0
  141. package/src/design-system/default-adapter.tsx +52 -23
  142. package/src/design-system/types.ts +4 -1
  143. package/src/index.ts +0 -1
  144. package/src/styles.css +1 -0
  145. package/src/ui/command.spec.tsx +33 -1
  146. package/src/ui/command.tsx +52 -9
  147. package/dist/marketing/MarketingHome.d.ts +0 -36
  148. package/dist/marketing/MarketingHome.d.ts.map +0 -1
  149. package/dist/marketing/MarketingHome.js +0 -28
  150. package/dist/marketing/MarketingHome.js.map +0 -1
  151. package/dist/marketing/MarketingHome.spec.d.ts +0 -2
  152. package/dist/marketing/MarketingHome.spec.d.ts.map +0 -1
  153. package/dist/marketing/MarketingHome.spec.js +0 -44
  154. package/dist/marketing/MarketingHome.spec.js.map +0 -1
  155. package/dist/marketing/Starfield.d.ts +0 -8
  156. package/dist/marketing/Starfield.d.ts.map +0 -1
  157. package/dist/marketing/Starfield.js +0 -349
  158. package/dist/marketing/Starfield.js.map +0 -1
  159. package/dist/marketing/index.d.ts +0 -3
  160. package/dist/marketing/index.d.ts.map +0 -1
  161. package/dist/marketing/index.js +0 -3
  162. package/dist/marketing/index.js.map +0 -1
  163. package/src/marketing/MarketingHome.spec.tsx +0 -75
  164. package/src/marketing/MarketingHome.tsx +0 -183
  165. package/src/marketing/Starfield.tsx +0 -403
  166. package/src/marketing/index.ts +0 -6
@@ -0,0 +1,441 @@
1
+ import {
2
+ useEffect,
3
+ useRef,
4
+ useState,
5
+ type ReactNode,
6
+ type ReactElement,
7
+ } from "react";
8
+
9
+ import { formatAttachmentError } from "./attachment-accept.js";
10
+ import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
11
+
12
+ export interface ComposerContextPickerItem {
13
+ id: string;
14
+ title: string;
15
+ disabled?: boolean;
16
+ url?: string;
17
+ }
18
+ export interface ComposerContextPickerRequest {
19
+ search: string;
20
+ page: number;
21
+ cursor?: string;
22
+ url?: string;
23
+ signal: AbortSignal;
24
+ }
25
+ export interface ComposerContextPickerResult {
26
+ items: readonly ComposerContextPickerItem[];
27
+ hasMore?: boolean;
28
+ nextCursor?: string;
29
+ }
30
+ export type ComposerContextPickerSelection =
31
+ | void
32
+ | boolean
33
+ | Promise<void | boolean>;
34
+ export type ComposerContextPickerFooterAction = {
35
+ label: string;
36
+ icon?: ReactNode;
37
+ disabled?: boolean;
38
+ } & (
39
+ | { onSelect: () => ComposerContextPickerSelection; renderLink?: never }
40
+ | { renderLink: (children: ReactNode) => ReactElement; onSelect?: never }
41
+ );
42
+ export type ComposerContextPickerConfig = {
43
+ footerAction?: ComposerContextPickerFooterAction;
44
+ searchPlaceholder: string;
45
+ selectedIds?: readonly string[];
46
+ scopeKey?: string;
47
+ refreshKey?: string | number;
48
+ link?: {
49
+ label?: string;
50
+ placeholder: string;
51
+ submitLabel: string;
52
+ validate?: (url: string) => string | undefined;
53
+ };
54
+ clearSelection?: {
55
+ label: string;
56
+ onSelect: () => ComposerContextPickerSelection;
57
+ };
58
+ emptyMessage?: string;
59
+ } & (
60
+ | {
61
+ items?: readonly ComposerContextPickerItem[];
62
+ loading?: boolean;
63
+ error?: string;
64
+ onRetry?: () => unknown;
65
+ load?: never;
66
+ }
67
+ | {
68
+ load: (
69
+ request: ComposerContextPickerRequest,
70
+ ) => Promise<ComposerContextPickerResult>;
71
+ items?: never;
72
+ loading?: never;
73
+ error?: never;
74
+ onRetry?: never;
75
+ }
76
+ ) &
77
+ (
78
+ | {
79
+ presentation?: "submenu" | { type: "dialog"; mode: "url" };
80
+ onSelect: (
81
+ item: ComposerContextPickerItem,
82
+ request: ComposerContextPickerRequest,
83
+ ) => ComposerContextPickerSelection;
84
+ }
85
+ | {
86
+ presentation: {
87
+ type: "dialog";
88
+ mode: "multiple";
89
+ onAttach: (
90
+ items: readonly ComposerContextPickerItem[],
91
+ request: ComposerContextPickerRequest,
92
+ ) => ComposerContextPickerSelection;
93
+ };
94
+ onSelect?: never;
95
+ }
96
+ );
97
+
98
+ type PickerLocation = Omit<ComposerContextPickerRequest, "signal">;
99
+ type LoadState = {
100
+ key: string;
101
+ status: "loading" | "ready" | "error";
102
+ result?: ComposerContextPickerResult;
103
+ error?: string;
104
+ };
105
+
106
+ function validResult(result: ComposerContextPickerResult): boolean {
107
+ return Boolean(
108
+ result &&
109
+ Array.isArray(result.items) &&
110
+ result.items.every(
111
+ (item) =>
112
+ item &&
113
+ typeof item.id === "string" &&
114
+ typeof item.title === "string" &&
115
+ (item.disabled === undefined || typeof item.disabled === "boolean") &&
116
+ (item.url === undefined || typeof item.url === "string"),
117
+ ) &&
118
+ (result.hasMore === undefined || typeof result.hasMore === "boolean") &&
119
+ (result.nextCursor === undefined || typeof result.nextCursor === "string"),
120
+ );
121
+ }
122
+
123
+ export function useComposerContextPicker({
124
+ config,
125
+ onClose,
126
+ }: {
127
+ config: ComposerContextPickerConfig;
128
+ onClose: () => void;
129
+ }) {
130
+ const t = useComposerRuntimeAdapters().translate!;
131
+ const configRef = useRef(config);
132
+ configRef.current = config;
133
+ const translateRef = useRef(t);
134
+ translateRef.current = t;
135
+ const presentation =
136
+ typeof config.presentation === "object" ? config.presentation : undefined;
137
+ const [chosen, setChosen] = useState<
138
+ ReadonlyMap<string, ComposerContextPickerItem>
139
+ >(() => new Map());
140
+ const [stage, setStage] = useState<"link" | "results">(
141
+ config.link || presentation ? "link" : "results",
142
+ );
143
+ const [link, setLink] = useState("");
144
+ const [location, setLocation] = useState<PickerLocation>({
145
+ search: "",
146
+ page: 1,
147
+ });
148
+ const cursors = useRef<Array<string | undefined>>([undefined]);
149
+ const [revision, setRevision] = useState(0);
150
+ const [loaded, setLoaded] = useState<LoadState>({
151
+ key: "",
152
+ status: "loading",
153
+ });
154
+ const [actionError, setActionError] = useState<{
155
+ message: string;
156
+ retry?: () => void;
157
+ }>();
158
+ const [selecting, setSelecting] = useState(false);
159
+ const epoch = useRef(0);
160
+ const pendingSelection = useRef<AbortController | null>(null);
161
+ const remote = Boolean(config.load);
162
+ const key = JSON.stringify([location, config.refreshKey]);
163
+ const keyRef = useRef(key);
164
+ keyRef.current = key;
165
+
166
+ const invalidateSelection = () => {
167
+ epoch.current++;
168
+ pendingSelection.current?.abort();
169
+ pendingSelection.current = null;
170
+ setSelecting(false);
171
+ setActionError(undefined);
172
+ };
173
+ useEffect(
174
+ () => () => {
175
+ epoch.current++;
176
+ pendingSelection.current?.abort();
177
+ },
178
+ [],
179
+ );
180
+
181
+ useEffect(() => {
182
+ if (!remote || stage !== "results") return;
183
+ const abort = new AbortController();
184
+ const loader = configRef.current.load!;
185
+ const request = { ...location, signal: abort.signal };
186
+ setLoaded((previous) => ({
187
+ key,
188
+ status: "loading",
189
+ result: previous.result,
190
+ }));
191
+ void Promise.resolve()
192
+ .then(() => {
193
+ if (abort.signal.aborted) return;
194
+ return loader(request);
195
+ })
196
+ .then((result) => {
197
+ if (abort.signal.aborted || keyRef.current !== key) return;
198
+ if (!result || !validResult(result))
199
+ throw new Error(
200
+ translateRef.current("agentChat.composer.contextLoadFailed", {
201
+ defaultValue: "Could not load context.",
202
+ }),
203
+ );
204
+ setLoaded({ key, status: "ready", result });
205
+ })
206
+ .catch((cause: unknown) => {
207
+ if (abort.signal.aborted || keyRef.current !== key) return;
208
+ setLoaded({
209
+ key,
210
+ status: "error",
211
+ error: formatAttachmentError(
212
+ cause,
213
+ translateRef.current("agentChat.composer.contextLoadFailed", {
214
+ defaultValue: "Could not load context.",
215
+ }),
216
+ ),
217
+ });
218
+ });
219
+ return () => abort.abort();
220
+ }, [key, location, remote, stage, revision]);
221
+
222
+ const move = (next: PickerLocation) => {
223
+ invalidateSelection();
224
+ setLocation(next);
225
+ };
226
+ const back = () => {
227
+ invalidateSelection();
228
+ if (stage === "results" && (config.link || presentation)) {
229
+ setStage("link");
230
+ setChosen(new Map());
231
+ setLocation({ search: "", page: 1 });
232
+ cursors.current = [undefined];
233
+ }
234
+ };
235
+ const select = async (
236
+ item?: ComposerContextPickerItem | "footer" | "batch" | "url",
237
+ ) => {
238
+ if (pendingSelection.current) return;
239
+ const abort = new AbortController();
240
+ const generation = ++epoch.current;
241
+ pendingSelection.current = abort;
242
+ setSelecting(true);
243
+ setActionError(undefined);
244
+ try {
245
+ const active = configRef.current;
246
+ const request = Object.freeze({
247
+ ...location,
248
+ ...(item === "url" ? { url: link.trim() } : {}),
249
+ signal: abort.signal,
250
+ });
251
+ const dialog =
252
+ typeof active.presentation === "object"
253
+ ? active.presentation
254
+ : undefined;
255
+ const batch = Object.freeze(
256
+ [...chosen.values()]
257
+ .filter((value) => !active.selectedIds?.includes(value.id))
258
+ .map((value) => Object.freeze({ ...value })),
259
+ );
260
+ const result =
261
+ item === "batch" && dialog?.mode === "multiple"
262
+ ? await dialog.onAttach(batch, request)
263
+ : item === "url"
264
+ ? await active.onSelect!(
265
+ { id: link.trim(), title: link.trim(), url: link.trim() },
266
+ request,
267
+ )
268
+ : item === "footer"
269
+ ? await configRef.current.footerAction?.onSelect?.()
270
+ : item && typeof item === "object"
271
+ ? await active.onSelect!(item, request)
272
+ : await configRef.current.clearSelection?.onSelect();
273
+ if (abort.signal.aborted || epoch.current !== generation) return;
274
+ if (result !== false) onClose();
275
+ } catch (cause) {
276
+ if (abort.signal.aborted || epoch.current !== generation) return;
277
+ setActionError({
278
+ message: formatAttachmentError(
279
+ cause,
280
+ t("agentChat.composer.contextActionFailed", {
281
+ defaultValue: "Could not add context.",
282
+ }),
283
+ ),
284
+ retry: () => {
285
+ void select(item);
286
+ },
287
+ });
288
+ } finally {
289
+ if (epoch.current === generation) {
290
+ pendingSelection.current = null;
291
+ setSelecting(false);
292
+ }
293
+ }
294
+ };
295
+ const retryLoad = async () => {
296
+ setActionError(undefined);
297
+ if (remote) {
298
+ setRevision((value) => value + 1);
299
+ return;
300
+ }
301
+ const generation = epoch.current;
302
+ try {
303
+ await configRef.current.onRetry?.();
304
+ } catch (cause) {
305
+ if (epoch.current !== generation) return;
306
+ setActionError({
307
+ message: formatAttachmentError(
308
+ cause,
309
+ t("agentChat.composer.contextLoadFailed", {
310
+ defaultValue: "Could not load context.",
311
+ }),
312
+ ),
313
+ retry: () => {
314
+ void retryLoad();
315
+ },
316
+ });
317
+ }
318
+ };
319
+ const validateLink = (value: string): string | undefined => {
320
+ try {
321
+ if (!value)
322
+ return t("agentChat.composer.contextLinkRequired", {
323
+ defaultValue: "Enter a link.",
324
+ });
325
+ if (presentation) {
326
+ let parsed: URL;
327
+ try {
328
+ parsed = new URL(value);
329
+ } catch {
330
+ return t("agentChat.composer.contextInvalidUrl", {
331
+ defaultValue: "Enter a valid HTTP or HTTPS URL.",
332
+ });
333
+ }
334
+ if (
335
+ !["http:", "https:"].includes(parsed.protocol) ||
336
+ !parsed.hostname ||
337
+ parsed.username ||
338
+ parsed.password
339
+ ) {
340
+ return t("agentChat.composer.contextInvalidUrl", {
341
+ defaultValue: "Enter a valid HTTP or HTTPS URL.",
342
+ });
343
+ }
344
+ }
345
+ return config.link?.validate?.(value);
346
+ } catch (cause) {
347
+ return formatAttachmentError(
348
+ cause,
349
+ t("agentChat.composer.contextLoadFailed", {
350
+ defaultValue: "Could not load context.",
351
+ }),
352
+ );
353
+ }
354
+ };
355
+ const linkError = validateLink(link.trim());
356
+ const submitLink = () => {
357
+ const value = link.trim();
358
+ const message = validateLink(value);
359
+ if (presentation && message) return;
360
+ if (message) {
361
+ setActionError({ message });
362
+ return;
363
+ }
364
+ if (presentation?.mode === "url") {
365
+ void select("url");
366
+ return;
367
+ }
368
+ move({ search: "", page: 1, url: value });
369
+ cursors.current = [undefined];
370
+ setStage("results");
371
+ };
372
+
373
+ const loading = remote
374
+ ? loaded.key !== key || loaded.status === "loading"
375
+ : config.loading === true;
376
+ const error =
377
+ actionError?.message ??
378
+ (remote
379
+ ? loaded.key === key && loaded.status === "error"
380
+ ? loaded.error
381
+ : undefined
382
+ : config.error);
383
+ const result = remote ? loaded.result : undefined;
384
+ const terms = location.search
385
+ .trim()
386
+ .toLocaleLowerCase()
387
+ .split(/\s+/)
388
+ .filter(Boolean);
389
+ const items = remote
390
+ ? (result?.items ?? [])
391
+ : (config.items ?? []).filter((item) =>
392
+ terms.every((term) => item.title.toLocaleLowerCase().includes(term)),
393
+ );
394
+ const hasMore = result?.hasMore ?? Boolean(result?.nextCursor);
395
+ const retry =
396
+ actionError?.retry ??
397
+ (remote || config.onRetry
398
+ ? () => {
399
+ void retryLoad();
400
+ }
401
+ : undefined);
402
+ const selectedItems = [...chosen.values()].filter(
403
+ (item) => !config.selectedIds?.includes(item.id),
404
+ );
405
+ const toggleItem = (item: ComposerContextPickerItem, checked: boolean) => {
406
+ if (selecting || item.disabled || config.selectedIds?.includes(item.id))
407
+ return;
408
+ setActionError(undefined);
409
+ setChosen((previous) => {
410
+ const next = new Map(previous);
411
+ if (checked) next.set(item.id, item);
412
+ else next.delete(item.id);
413
+ return next;
414
+ });
415
+ };
416
+ return {
417
+ t,
418
+ config,
419
+ stage,
420
+ link,
421
+ setLink,
422
+ location,
423
+ cursors,
424
+ move,
425
+ back,
426
+ select,
427
+ submitLink,
428
+ loading,
429
+ error,
430
+ items,
431
+ hasMore,
432
+ retry,
433
+ selecting,
434
+ actionError,
435
+ setActionError,
436
+ result,
437
+ linkError,
438
+ selectedItems,
439
+ toggleItem,
440
+ };
441
+ }
@@ -121,6 +121,29 @@ describe("default design system adapter", () => {
121
121
  expect(preventDefault).toHaveBeenCalledOnce();
122
122
  });
123
123
 
124
+ it("keeps outside dismissal enabled when the close control is hidden", () => {
125
+ const dialog = renderComponent(defaultDesignSystemComponents.Dialog, {
126
+ open: true,
127
+ onOpenChange: vi.fn(),
128
+ title: "Preview",
129
+ children: <p>Preview content</p>,
130
+ hideClose: true,
131
+ });
132
+ const content = findElement(
133
+ dialog,
134
+ (element) => typeof element.props.onInteractOutside === "function",
135
+ );
136
+ const preventDefault = vi.fn();
137
+
138
+ (
139
+ content?.props.onInteractOutside as
140
+ | ((event: { preventDefault: () => void }) => void)
141
+ | undefined
142
+ )?.({ preventDefault });
143
+
144
+ expect(preventDefault).not.toHaveBeenCalled();
145
+ });
146
+
124
147
  it("honors menu selection state and closeOnAction", () => {
125
148
  const onAction = vi.fn();
126
149
  const menu = renderComponent(defaultDesignSystemComponents.Menu, {
@@ -612,9 +612,11 @@ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
612
612
  description,
613
613
  children,
614
614
  footer,
615
+ headerAction,
615
616
  trigger,
616
617
  size = "medium",
617
618
  dismissible = true,
619
+ hideClose,
618
620
  closeLabel,
619
621
  initialFocusRef,
620
622
  restoreFocusRef,
@@ -626,13 +628,16 @@ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
626
628
  {trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
627
629
  <DialogContent
628
630
  container={portalContainer}
629
- hideClose={!dismissible}
631
+ hideClose={hideClose || !dismissible}
630
632
  closeLabel={closeLabel}
633
+ {...(!description ? { "aria-describedby": undefined } : {})}
631
634
  style={style}
632
635
  className={cn(
633
636
  size === "small" && "max-w-sm",
634
637
  size === "medium" && "max-w-lg",
635
638
  size === "large" && "max-w-2xl",
639
+ size === "viewport" &&
640
+ "h-[90dvh] max-h-[90dvh] w-[calc(100vw-2rem)] max-w-[96rem] sm:w-[calc(100vw-3rem)]",
636
641
  size === "fullscreen" &&
637
642
  "inset-0 h-[100dvh] max-h-none w-full max-w-none translate-x-0 translate-y-0 rounded-none",
638
643
  className,
@@ -655,8 +660,17 @@ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
655
660
  if (!dismissible) event.preventDefault();
656
661
  }}
657
662
  >
658
- <DialogHeader>
659
- <DialogTitle>{title}</DialogTitle>
663
+ <DialogHeader
664
+ className={
665
+ headerAction
666
+ ? "flex-row items-center justify-between gap-3 space-y-0"
667
+ : undefined
668
+ }
669
+ >
670
+ <DialogTitle className={headerAction ? "min-w-0" : undefined}>
671
+ {title}
672
+ </DialogTitle>
673
+ {headerAction}
660
674
  {description ? (
661
675
  <DialogDescription>{description}</DialogDescription>
662
676
  ) : null}
@@ -874,22 +888,12 @@ const DefaultTabs: DesignSystemComponents["Tabs"] = ({
874
888
  onChange,
875
889
  orientation,
876
890
  activationMode,
891
+ headerActions,
877
892
  className,
878
893
  style,
879
894
  ...props
880
- }) => (
881
- <DefaultTabsPrimitive
882
- {...props}
883
- value={String(value)}
884
- onValueChange={(next) => {
885
- const item = items.find((candidate) => String(candidate.value) === next);
886
- if (item) onChange(item.value);
887
- }}
888
- orientation={orientation}
889
- activationMode={activationMode}
890
- className={className}
891
- style={style}
892
- >
895
+ }) => {
896
+ const tabList = (
893
897
  <TabsList>
894
898
  {items.map((item) => (
895
899
  <TabsTrigger
@@ -902,13 +906,38 @@ const DefaultTabs: DesignSystemComponents["Tabs"] = ({
902
906
  </TabsTrigger>
903
907
  ))}
904
908
  </TabsList>
905
- {items.map((item) => (
906
- <TabsContent key={item.value} value={String(item.value)}>
907
- {item.content}
908
- </TabsContent>
909
- ))}
910
- </DefaultTabsPrimitive>
911
- );
909
+ );
910
+ return (
911
+ <DefaultTabsPrimitive
912
+ {...props}
913
+ value={String(value)}
914
+ onValueChange={(next) => {
915
+ const item = items.find(
916
+ (candidate) => String(candidate.value) === next,
917
+ );
918
+ if (item) onChange(item.value);
919
+ }}
920
+ orientation={orientation}
921
+ activationMode={activationMode}
922
+ className={className}
923
+ style={style}
924
+ >
925
+ {headerActions ? (
926
+ <div className="flex flex-wrap items-center justify-between gap-3">
927
+ {tabList}
928
+ {headerActions}
929
+ </div>
930
+ ) : (
931
+ tabList
932
+ )}
933
+ {items.map((item) => (
934
+ <TabsContent key={item.value} value={String(item.value)}>
935
+ {item.content}
936
+ </TabsContent>
937
+ ))}
938
+ </DefaultTabsPrimitive>
939
+ );
940
+ };
912
941
 
913
942
  export const defaultDesignSystemComponents: DesignSystemComponents = {
914
943
  ActionButton: DefaultActionButton,
@@ -255,9 +255,11 @@ export interface DialogProps extends DesignSystemOverlayProps {
255
255
  children: ReactNode;
256
256
  description?: ReactNode;
257
257
  footer?: ReactNode;
258
+ headerAction?: ReactNode;
258
259
  trigger?: ReactElement;
259
- size?: "small" | "medium" | "large" | "fullscreen";
260
+ size?: "small" | "medium" | "large" | "viewport" | "fullscreen";
260
261
  dismissible?: boolean;
262
+ hideClose?: boolean;
261
263
  closeLabel?: string;
262
264
  initialFocusRef?: RefObject<HTMLElement | null>;
263
265
  restoreFocusRef?: RefObject<HTMLElement | null>;
@@ -334,6 +336,7 @@ export interface TabsProps<Value extends DesignSystemKey = string>
334
336
  onChange: (value: Value) => void;
335
337
  orientation?: "horizontal" | "vertical";
336
338
  activationMode?: "automatic" | "manual";
339
+ headerActions?: ReactNode;
337
340
  }
338
341
 
339
342
  export interface PickerComponent {
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@ export * from "./collab-ui/index.js";
4
4
  export * from "./dashboard/index.js";
5
5
  export * from "./hooks/index.js";
6
6
  export * from "./icons/index.js";
7
- export * from "./marketing/index.js";
8
7
  export * from "./onboarding/index.js";
9
8
  export * from "./provider.js";
10
9
  export * from "./sharing/index.js";
package/src/styles.css CHANGED
@@ -19,6 +19,7 @@
19
19
  * compiled `.js` next to it for consumers that only install `dist/`.
20
20
  */
21
21
  @import "./theme/resource-icons.css";
22
+ @import "./app-shell/prompt-home.css";
22
23
 
23
24
  @source "./**/*.{js,ts,tsx}";
24
25
 
@@ -1,7 +1,39 @@
1
1
  import type { ReactElement, ReactNode } from "react";
2
+ import { renderToStaticMarkup } from "react-dom/server";
2
3
  import { describe, expect, it } from "vitest";
3
4
 
4
- import { CommandDialog } from "./command.js";
5
+ import {
6
+ Command,
7
+ CommandDialog,
8
+ CommandInput,
9
+ CommandList,
10
+ MenuSearchInput,
11
+ } from "./command.js";
12
+
13
+ describe("integrated search semantics", () => {
14
+ it("uses a real searchbox for menus without dangling listbox relationships", () => {
15
+ const html = renderToStaticMarkup(
16
+ <MenuSearchInput aria-label="Search sources" />,
17
+ );
18
+ expect(html).toContain('role="searchbox"');
19
+ expect(html).not.toContain('role="combobox"');
20
+ expect(html).not.toContain("aria-controls");
21
+ expect(html).toContain("border-b");
22
+ });
23
+ it("preserves the existing CommandInput combobox and associated CommandList", () => {
24
+ const html = renderToStaticMarkup(
25
+ <Command>
26
+ <CommandInput />
27
+ <CommandList />
28
+ </Command>,
29
+ );
30
+ expect(html).toContain('role="combobox"');
31
+ expect(html).toContain('role="listbox"');
32
+ const controls = /aria-controls="([^"]+)"/.exec(html)?.[1];
33
+ expect(controls).toBeDefined();
34
+ expect(html).toContain(`id="${controls}"`);
35
+ });
36
+ });
5
37
 
6
38
  interface CommandDialogElement extends ReactElement {
7
39
  props: {