@agent-native/core 0.137.0 → 0.137.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 (28) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/AgentCompletionSound.tsx +105 -0
  3. package/corpus/templates/analytics/app/components/layout/Layout.tsx +44 -35
  4. package/corpus/templates/analytics/app/i18n-data.ts +38 -0
  5. package/corpus/templates/analytics/app/pages/Settings.tsx +53 -1
  6. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +6 -0
  7. package/corpus/templates/analytics/changelog/2026-08-04-choose-whether-analytics-plays-a-bell-when-the-agent-finishe.md +6 -0
  8. package/corpus/templates/analytics/shared/analytics-user-prefs.ts +2 -0
  9. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +321 -0
  10. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +32 -11
  11. package/corpus/templates/slides/app/lib/recent-references.ts +82 -0
  12. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  13. package/corpus/templates/slides/app/pages/Index.tsx +236 -181
  14. package/dist/client/DefaultSpinner.d.ts +0 -12
  15. package/dist/client/DefaultSpinner.js +22 -2
  16. package/dist/client/onboarding/use-onboarding.js +3 -0
  17. package/dist/collab/awareness.d.ts +2 -2
  18. package/dist/collab/routes.d.ts +1 -1
  19. package/dist/deploy/build.js +2 -1
  20. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  21. package/dist/notifications/routes.d.ts +2 -2
  22. package/dist/observability/routes.d.ts +3 -3
  23. package/dist/resources/handlers.d.ts +1 -1
  24. package/dist/secrets/routes.d.ts +3 -3
  25. package/dist/server/transcribe-voice.d.ts +1 -1
  26. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
  27. package/package.json +1 -1
  28. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
@@ -0,0 +1,321 @@
1
+ import {
2
+ IconArrowRight,
3
+ IconPalette,
4
+ IconPresentation,
5
+ IconUpload,
6
+ } from "@tabler/icons-react";
7
+ import type { ChangeEvent, ReactNode } from "react";
8
+
9
+ import { Button } from "@/components/ui/button";
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogDescription,
14
+ DialogHeader,
15
+ DialogTitle,
16
+ } from "@/components/ui/dialog";
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectGroup,
21
+ SelectItem,
22
+ SelectLabel,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from "@/components/ui/select";
26
+ import type { Deck } from "@/context/DeckContext";
27
+ import type { RecentReference } from "@/lib/recent-references";
28
+ import { cn } from "@/lib/utils";
29
+
30
+ export interface NewDeckReferenceSelection {
31
+ designSystemId?: string | null;
32
+ referenceDeckId?: string | null;
33
+ }
34
+
35
+ interface DesignSystemOption {
36
+ id: string;
37
+ title: string;
38
+ isDefault?: boolean;
39
+ }
40
+
41
+ interface NewDeckReferenceStepProps {
42
+ open: boolean;
43
+ designSystems: DesignSystemOption[];
44
+ decks: Deck[];
45
+ defaultDesignSystemId: string | null;
46
+ defaultReferenceDeckId: string | null;
47
+ recentReferences: RecentReference[];
48
+ onSelect: (selection: NewDeckReferenceSelection) => void;
49
+ onImport: (files: File[]) => Promise<void>;
50
+ onSkip: () => void;
51
+ onOpenChange: (open: boolean) => void;
52
+ importing?: boolean;
53
+ title: string;
54
+ designSystemLabel: string;
55
+ referenceDeckLabel: string;
56
+ chooseDeckLabel: string;
57
+ importFileLabel: string;
58
+ importingLabel: string;
59
+ skipLabel: string;
60
+ defaultSuffix: string;
61
+ starredLabel: string;
62
+ otherDecksLabel: string;
63
+ description: string;
64
+ promptNote?: string;
65
+ }
66
+
67
+ export function NewDeckReferenceStep({
68
+ open,
69
+ designSystems,
70
+ decks,
71
+ defaultDesignSystemId,
72
+ defaultReferenceDeckId,
73
+ recentReferences,
74
+ onSelect,
75
+ onImport,
76
+ onSkip,
77
+ onOpenChange,
78
+ importing = false,
79
+ title,
80
+ designSystemLabel,
81
+ referenceDeckLabel,
82
+ chooseDeckLabel,
83
+ importFileLabel,
84
+ importingLabel,
85
+ skipLabel,
86
+ defaultSuffix,
87
+ starredLabel,
88
+ otherDecksLabel,
89
+ description,
90
+ promptNote,
91
+ }: NewDeckReferenceStepProps) {
92
+ const designSystemById = new Map(
93
+ designSystems.map((designSystem) => [designSystem.id, designSystem]),
94
+ );
95
+ const deckById = new Map(decks.map((deck) => [deck.id, deck]));
96
+ const recentOptions = recentReferences
97
+ .map((reference) => {
98
+ const item =
99
+ reference.kind === "design-system"
100
+ ? designSystemById.get(reference.id)
101
+ : deckById.get(reference.id);
102
+ return item ? { reference, item } : null;
103
+ })
104
+ .filter(
105
+ (
106
+ value,
107
+ ): value is {
108
+ reference: RecentReference;
109
+ item: DesignSystemOption | Deck;
110
+ } => value !== null,
111
+ );
112
+ const defaultReferenceKeys = new Set(
113
+ [
114
+ defaultDesignSystemId ? `design-system:${defaultDesignSystemId}` : null,
115
+ defaultReferenceDeckId ? `deck:${defaultReferenceDeckId}` : null,
116
+ ].filter((value): value is string => value !== null),
117
+ );
118
+ const visibleRecentOptions = recentOptions.filter(
119
+ ({ reference }) =>
120
+ !defaultReferenceKeys.has(`${reference.kind}:${reference.id}`),
121
+ );
122
+ const starredDecks = decks.filter((deck) => deck.starred);
123
+ const otherDecks = decks.filter((deck) => !deck.starred);
124
+
125
+ const handleImport = async (event: ChangeEvent<HTMLInputElement>) => {
126
+ const files = Array.from(event.target.files ?? []);
127
+ event.target.value = "";
128
+ if (files.length === 0) return;
129
+ await onImport(files);
130
+ };
131
+
132
+ return (
133
+ <Dialog open={open} onOpenChange={onOpenChange}>
134
+ <DialogContent className="max-h-[min(720px,calc(100vh-32px))] overflow-y-auto sm:max-w-2xl">
135
+ <DialogHeader>
136
+ <DialogTitle>{title}</DialogTitle>
137
+ <DialogDescription>{description}</DialogDescription>
138
+ </DialogHeader>
139
+
140
+ <div className="grid gap-4">
141
+ {(defaultDesignSystemId || defaultReferenceDeckId) && (
142
+ <div className="grid gap-2 sm:grid-cols-2">
143
+ {defaultDesignSystemId &&
144
+ designSystemById.has(defaultDesignSystemId) && (
145
+ <ReferenceCard
146
+ icon={<IconPalette className="size-4" />}
147
+ label={
148
+ designSystemById.get(defaultDesignSystemId)?.title ??
149
+ designSystemLabel
150
+ }
151
+ meta={`${designSystemLabel}${defaultSuffix}`}
152
+ onClick={() =>
153
+ onSelect({
154
+ designSystemId: defaultDesignSystemId,
155
+ referenceDeckId: null,
156
+ })
157
+ }
158
+ />
159
+ )}
160
+ {defaultReferenceDeckId &&
161
+ deckById.has(defaultReferenceDeckId) && (
162
+ <ReferenceCard
163
+ icon={<IconPresentation className="size-4" />}
164
+ label={deckById.get(defaultReferenceDeckId)?.title ?? ""}
165
+ meta={`${referenceDeckLabel}${defaultSuffix}`}
166
+ onClick={() =>
167
+ onSelect({
168
+ designSystemId: null,
169
+ referenceDeckId: defaultReferenceDeckId,
170
+ })
171
+ }
172
+ />
173
+ )}
174
+ </div>
175
+ )}
176
+
177
+ {visibleRecentOptions.length > 0 && (
178
+ <div className="grid gap-2">
179
+ <div className="grid gap-2 sm:grid-cols-2">
180
+ {visibleRecentOptions.map(({ reference, item }) => (
181
+ <ReferenceCard
182
+ key={`${reference.kind}:${reference.id}`}
183
+ icon={
184
+ reference.kind === "design-system" ? (
185
+ <IconPalette className="size-4" />
186
+ ) : (
187
+ <IconPresentation className="size-4" />
188
+ )
189
+ }
190
+ label={item.title}
191
+ meta={
192
+ reference.kind === "design-system"
193
+ ? designSystemLabel
194
+ : referenceDeckLabel
195
+ }
196
+ onClick={() =>
197
+ onSelect(
198
+ reference.kind === "design-system"
199
+ ? {
200
+ designSystemId: reference.id,
201
+ referenceDeckId: null,
202
+ }
203
+ : {
204
+ designSystemId: null,
205
+ referenceDeckId: reference.id,
206
+ },
207
+ )
208
+ }
209
+ />
210
+ ))}
211
+ </div>
212
+ </div>
213
+ )}
214
+
215
+ {decks.length > 0 && (
216
+ <div className="grid gap-2">
217
+ <span className="text-xs font-medium text-muted-foreground">
218
+ {referenceDeckLabel}
219
+ </span>
220
+ <Select
221
+ onValueChange={(value) =>
222
+ onSelect({ designSystemId: null, referenceDeckId: value })
223
+ }
224
+ >
225
+ <SelectTrigger className="w-full">
226
+ <SelectValue placeholder={chooseDeckLabel} />
227
+ </SelectTrigger>
228
+ <SelectContent>
229
+ {starredDecks.length > 0 && (
230
+ <SelectGroup>
231
+ <SelectLabel>{starredLabel}</SelectLabel>
232
+ {starredDecks.map((deck) => (
233
+ <SelectItem key={deck.id} value={deck.id}>
234
+ {deck.title}
235
+ </SelectItem>
236
+ ))}
237
+ </SelectGroup>
238
+ )}
239
+ {otherDecks.length > 0 && (
240
+ <SelectGroup>
241
+ <SelectLabel>{otherDecksLabel}</SelectLabel>
242
+ {otherDecks.map((deck) => (
243
+ <SelectItem key={deck.id} value={deck.id}>
244
+ {deck.title}
245
+ </SelectItem>
246
+ ))}
247
+ </SelectGroup>
248
+ )}
249
+ </SelectContent>
250
+ </Select>
251
+ </div>
252
+ )}
253
+
254
+ <div className="flex flex-wrap items-center gap-2 border-t border-border pt-3">
255
+ <label
256
+ className={cn(
257
+ "inline-flex cursor-pointer items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors hover:bg-accent",
258
+ importing && "pointer-events-none opacity-60",
259
+ )}
260
+ >
261
+ <IconUpload className="size-4" />
262
+ {importing ? importingLabel : importFileLabel}
263
+ <input
264
+ type="file"
265
+ className="sr-only"
266
+ accept=".pptx,.pdf,.docx"
267
+ multiple
268
+ disabled={importing}
269
+ onChange={(event) => void handleImport(event)}
270
+ />
271
+ </label>
272
+ </div>
273
+ </div>
274
+
275
+ {promptNote && (
276
+ <p className="truncate border-t border-border pt-3 text-xs text-muted-foreground">
277
+ {promptNote}
278
+ </p>
279
+ )}
280
+
281
+ <div className="flex items-center justify-between border-t border-border pt-3">
282
+ <Button type="button" variant="ghost" onClick={onSkip}>
283
+ {skipLabel}
284
+ </Button>
285
+ <IconArrowRight className="size-4 text-muted-foreground" />
286
+ </div>
287
+ </DialogContent>
288
+ </Dialog>
289
+ );
290
+ }
291
+
292
+ function ReferenceCard({
293
+ icon,
294
+ label,
295
+ meta,
296
+ onClick,
297
+ }: {
298
+ icon: ReactNode;
299
+ label: string;
300
+ meta: string;
301
+ onClick: () => void;
302
+ }) {
303
+ return (
304
+ <button
305
+ type="button"
306
+ onClick={onClick}
307
+ className="flex min-w-0 items-center gap-3 rounded-lg border border-border bg-card px-3 py-3 text-start transition-colors hover:border-foreground/30 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
308
+ >
309
+ <span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-accent text-muted-foreground">
310
+ {icon}
311
+ </span>
312
+ <span className="min-w-0">
313
+ <span className="block truncate text-sm font-medium">{label}</span>
314
+ <span className="block truncate text-xs text-muted-foreground">
315
+ {meta}
316
+ </span>
317
+ </span>
318
+ <IconArrowRight className="ms-auto size-4 shrink-0 text-muted-foreground" />
319
+ </button>
320
+ );
321
+ }
@@ -16,6 +16,37 @@ export interface UploadedFile {
16
16
  size: number;
17
17
  }
18
18
 
19
+ export async function uploadPromptFiles(
20
+ files: File[],
21
+ ): Promise<UploadedFile[]> {
22
+ if (files.length === 0) return [];
23
+ const formData = new FormData();
24
+ files.forEach((file) => formData.append("files", file));
25
+ const response = await fetch(`${appBasePath()}/api/uploads`, {
26
+ method: "POST",
27
+ body: formData,
28
+ });
29
+ if (!response.ok) {
30
+ let message = "Upload failed";
31
+ try {
32
+ const data: unknown = await response.json();
33
+ if (
34
+ data &&
35
+ typeof data === "object" &&
36
+ "error" in data &&
37
+ typeof data.error === "string" &&
38
+ data.error.trim()
39
+ ) {
40
+ message = data.error;
41
+ }
42
+ } catch (error) {
43
+ throw new Error(`Upload failed (${response.status})`, { cause: error });
44
+ }
45
+ throw new Error(message);
46
+ }
47
+ return (await response.json()) as UploadedFile[];
48
+ }
49
+
19
50
  /**
20
51
  * Radix popovers portal to `document.body`, so a mousedown inside the model
21
52
  * picker or attachment menu reads as "outside" any panel that hosts a composer.
@@ -137,17 +168,7 @@ export default function PromptPopover({
137
168
  if (files.length === 0) return [];
138
169
  setUploading(true);
139
170
  try {
140
- const formData = new FormData();
141
- files.forEach((f) => formData.append("files", f));
142
- const res = await fetch(`${appBasePath()}/api/uploads`, {
143
- method: "POST",
144
- body: formData,
145
- });
146
- if (!res.ok) {
147
- const data = await res.json().catch(() => null);
148
- throw new Error(data?.error || "Upload failed");
149
- }
150
- return (await res.json()) as UploadedFile[];
171
+ return await uploadPromptFiles(files);
151
172
  } finally {
152
173
  setUploading(false);
153
174
  }
@@ -0,0 +1,82 @@
1
+ export const RECENT_REFERENCES_STORAGE_KEY = "slides:recent-references";
2
+
3
+ export type RecentReferenceKind = "deck" | "design-system";
4
+
5
+ export interface RecentReference {
6
+ id: string;
7
+ kind: RecentReferenceKind;
8
+ lastUsedAt: number;
9
+ }
10
+
11
+ export interface RecentReferencesResult {
12
+ items: RecentReference[];
13
+ readable: boolean;
14
+ }
15
+
16
+ const MAX_RECENT_REFERENCES = 8;
17
+
18
+ function isRecentReference(value: unknown): value is RecentReference {
19
+ if (!value || typeof value !== "object") return false;
20
+ const candidate = value as Partial<RecentReference>;
21
+ return (
22
+ typeof candidate.id === "string" &&
23
+ candidate.id.length > 0 &&
24
+ (candidate.kind === "deck" || candidate.kind === "design-system") &&
25
+ typeof candidate.lastUsedAt === "number" &&
26
+ Number.isFinite(candidate.lastUsedAt)
27
+ );
28
+ }
29
+
30
+ export function readRecentReferences(
31
+ storage: Pick<Storage, "getItem"> | null | undefined = typeof window ===
32
+ "undefined"
33
+ ? null
34
+ : window.localStorage,
35
+ ): RecentReferencesResult {
36
+ if (!storage) return { items: [], readable: true };
37
+
38
+ let raw: string | null;
39
+ try {
40
+ raw = storage.getItem(RECENT_REFERENCES_STORAGE_KEY);
41
+ } catch {
42
+ return { items: [], readable: false };
43
+ }
44
+
45
+ if (raw === null) return { items: [], readable: true };
46
+
47
+ try {
48
+ const parsed: unknown = JSON.parse(raw);
49
+ if (!Array.isArray(parsed)) return { items: [], readable: false };
50
+ return {
51
+ items: parsed.filter(isRecentReference).slice(0, MAX_RECENT_REFERENCES),
52
+ readable: true,
53
+ };
54
+ } catch {
55
+ return { items: [], readable: false };
56
+ }
57
+ }
58
+
59
+ export function rememberRecentReference(
60
+ reference: Omit<RecentReference, "lastUsedAt">,
61
+ storage:
62
+ | Pick<Storage, "getItem" | "setItem">
63
+ | null
64
+ | undefined = typeof window === "undefined" ? null : window.localStorage,
65
+ ): RecentReferencesResult {
66
+ const current = readRecentReferences(storage);
67
+ if (!storage || !current.readable) return current;
68
+
69
+ const next: RecentReference[] = [
70
+ { ...reference, lastUsedAt: Date.now() },
71
+ ...current.items.filter(
72
+ (item) => !(item.id === reference.id && item.kind === reference.kind),
73
+ ),
74
+ ].slice(0, MAX_RECENT_REFERENCES);
75
+
76
+ try {
77
+ storage.setItem(RECENT_REFERENCES_STORAGE_KEY, JSON.stringify(next));
78
+ return { items: next, readable: true };
79
+ } catch {
80
+ return { items: current.items, readable: false };
81
+ }
82
+ }
@@ -296,6 +296,9 @@ export default function DeckEditor() {
296
296
  handleSubmit: handleQuestionSubmit,
297
297
  handleSkip: handleQuestionSkip,
298
298
  } = useGuidedQuestionFlow({
299
+ stateKey: "guided-questions",
300
+ browserTabId: TAB_ID,
301
+ queryKey: ["guided-questions"],
299
302
  submitMessage: "Here are my answers — go ahead and create the slides.",
300
303
  skipMessage:
301
304
  "Skip the questions — just go ahead and create the slides with your best judgment.",