@agent-native/core 0.148.0 → 0.149.0

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 (165) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
  4. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
  5. package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
  6. package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
  8. package/corpus/templates/calendar/vitest.config.ts +6 -1
  9. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
  10. package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
  11. package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
  13. package/corpus/templates/content/actions/_property-utils.ts +11 -1
  14. package/corpus/templates/content/actions/view-screen.ts +62 -12
  15. package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
  16. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
  17. package/corpus/templates/design/AGENTS.md +3 -0
  18. package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
  19. package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
  20. package/corpus/templates/design/actions/get-design-template.ts +178 -0
  21. package/corpus/templates/design/actions/list-designs.ts +3 -1
  22. package/corpus/templates/design/actions/view-screen.ts +31 -0
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
  24. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  25. package/corpus/templates/design/app/i18n-data.ts +78 -0
  26. package/corpus/templates/design/app/lib/design-authors.ts +64 -0
  27. package/corpus/templates/design/app/pages/Index.tsx +114 -4
  28. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
  29. package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
  30. package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
  31. package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
  32. package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
  33. package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
  34. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
  35. package/corpus/templates/forms/AGENTS.md +2 -2
  36. package/corpus/templates/forms/actions/create-form.ts +1 -1
  37. package/corpus/templates/forms/actions/update-form.ts +5 -5
  38. package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
  39. package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
  40. package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
  41. package/corpus/templates/slides/AGENTS.md +4 -3
  42. package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
  43. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +12 -74
  44. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
  45. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
  46. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
  47. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -0
  48. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
  49. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  50. package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
  51. package/corpus/templates/slides/app/pages/DeckEditor.tsx +32 -14
  52. package/corpus/templates/slides/app/pages/Index.tsx +110 -128
  53. package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
  54. package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
  55. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
  56. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
  57. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  58. package/dist/agent/engine/registry.d.ts +8 -2
  59. package/dist/agent/engine/registry.js +63 -29
  60. package/dist/agent/production-agent.js +7 -0
  61. package/dist/agent/run-manager.d.ts +5 -4
  62. package/dist/agent/run-manager.js +6 -5
  63. package/dist/cli/claude-code-participant.d.ts +2 -1
  64. package/dist/cli/claude-code-participant.js +19 -0
  65. package/dist/cli/code-agent-executor.js +223 -0
  66. package/dist/cli/create-workspace.js +3 -3
  67. package/dist/cli/create.d.ts +3 -2
  68. package/dist/cli/create.js +20 -24
  69. package/dist/client/AgentPanel.js +8 -4
  70. package/dist/client/AssistantChat.js +34 -1
  71. package/dist/client/agent-chat-adapter.d.ts +1 -0
  72. package/dist/client/agent-chat-adapter.js +10 -2
  73. package/dist/client/blocks/library/diagram.js +1 -1
  74. package/dist/client/blocks/library/sanitize-html.js +66 -2
  75. package/dist/client/blocks/library/wireframe-kit.js +69 -0
  76. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  77. package/dist/client/chat/markdown-renderer.js +4 -0
  78. package/dist/client/chat/message-components.d.ts +1 -0
  79. package/dist/client/chat/message-components.js +9 -1
  80. package/dist/client/chat/tool-call-display.d.ts +8 -0
  81. package/dist/client/chat/tool-call-display.js +30 -21
  82. package/dist/client/chat-first/app-pane.js +1 -1
  83. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  84. package/dist/client/chat-first/apps-rail.js +12 -12
  85. package/dist/client/chat-first/chat-history.d.ts +1 -1
  86. package/dist/client/chat-first/chat-history.js +2 -0
  87. package/dist/client/chat-first/copy.js +1 -0
  88. package/dist/client/chat-first/primary-nav.js +1 -1
  89. package/dist/client/chat-first/types.d.ts +1 -0
  90. package/dist/client/resources/McpConnectionSuggestion.js +2 -2
  91. package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
  92. package/dist/client/settings/SettingsTabsPage.js +2 -2
  93. package/dist/client/sse-event-processor.d.ts +8 -0
  94. package/dist/client/sse-event-processor.js +13 -6
  95. package/dist/collab/struct-routes.d.ts +1 -1
  96. package/dist/deploy/build.d.ts +5 -7
  97. package/dist/deploy/build.js +19 -12
  98. package/dist/localization/default-messages.js +1 -1
  99. package/dist/observability/routes.d.ts +3 -3
  100. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  101. package/dist/scripts/call-agent.js +3 -3
  102. package/dist/scripts/describe-workspace-apps.js +1 -1
  103. package/dist/secrets/routes.d.ts +6 -6
  104. package/dist/server/agent-capabilities.js +0 -0
  105. package/dist/server/agent-chat/context-tools.js +1 -1
  106. package/dist/server/agent-chat/prompt-resources.js +1 -1
  107. package/dist/server/agent-chat-plugin.js +1 -1
  108. package/dist/server/auth.js +190 -42
  109. package/dist/server/better-auth-migrations.js +53 -0
  110. package/dist/server/http-response-telemetry.js +7 -1
  111. package/dist/server/onboarding-html.js +119 -33
  112. package/dist/server/transcribe-voice.d.ts +1 -1
  113. package/dist/shared/password-policy.d.ts +2 -1
  114. package/dist/shared/password-policy.js +2 -1
  115. package/dist/styles/agent-conversation.css +20 -3
  116. package/dist/styles/agent-native.css +14 -2
  117. package/dist/styles/blocks.css +132 -0
  118. package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
  119. package/dist/templates/default/app/i18n/de-DE.ts +1 -1
  120. package/dist/templates/default/app/i18n/en-US.ts +1 -1
  121. package/dist/templates/default/app/i18n/es-ES.ts +1 -1
  122. package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
  123. package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
  124. package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
  125. package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
  126. package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
  127. package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
  128. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  129. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  130. package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
  131. package/docs/content/actions.mdx +4 -1
  132. package/docs/content/agent-surfaces.mdx +8 -6
  133. package/docs/content/automations.mdx +1 -1
  134. package/docs/content/cloneable-saas.mdx +6 -6
  135. package/docs/content/database.mdx +6 -1
  136. package/docs/content/deployment.mdx +10 -8
  137. package/docs/content/docs-components.mdx +22 -14
  138. package/docs/content/drop-in-agent.mdx +1 -1
  139. package/docs/content/environment-variables.mdx +4 -3
  140. package/docs/content/getting-started-actions.mdx +5 -4
  141. package/docs/content/getting-started-pages.mdx +7 -6
  142. package/docs/content/getting-started.mdx +4 -3
  143. package/docs/content/notifications.mdx +6 -6
  144. package/docs/content/progress.mdx +5 -5
  145. package/docs/content/routing.mdx +3 -1
  146. package/docs/content/server-database.mdx +13 -4
  147. package/docs/content/server-overview.mdx +9 -9
  148. package/docs/content/server-plugins.mdx +10 -4
  149. package/docs/content/server-routes.mdx +7 -2
  150. package/docs/content/tracking.mdx +4 -4
  151. package/docs/content/what-is-agent-native.mdx +4 -4
  152. package/package.json +2 -2
  153. package/src/templates/default/app/i18n/ar-SA.ts +1 -1
  154. package/src/templates/default/app/i18n/de-DE.ts +1 -1
  155. package/src/templates/default/app/i18n/en-US.ts +1 -1
  156. package/src/templates/default/app/i18n/es-ES.ts +1 -1
  157. package/src/templates/default/app/i18n/fr-FR.ts +1 -1
  158. package/src/templates/default/app/i18n/hi-IN.ts +1 -1
  159. package/src/templates/default/app/i18n/ja-JP.ts +1 -1
  160. package/src/templates/default/app/i18n/ko-KR.ts +1 -1
  161. package/src/templates/default/app/i18n/pt-BR.ts +1 -1
  162. package/src/templates/default/app/i18n/zh-CN.ts +1 -1
  163. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  164. package/src/templates/default/pnpm-workspace.yaml +1 -0
  165. package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
@@ -48,6 +48,17 @@ export interface NewDeckReferenceSelection {
48
48
  value: string;
49
49
  } | null;
50
50
  }
51
+
52
+ export type NewDeckReferenceSource = NonNullable<
53
+ NewDeckReferenceSelection["referenceSource"]
54
+ >;
55
+
56
+ export interface ImportedReference {
57
+ id: string;
58
+ title: string;
59
+ source: "pptx" | "pdf" | "google-slides";
60
+ }
61
+
51
62
  interface DesignSystemOption {
52
63
  id: string;
53
64
  title: string;
@@ -61,9 +72,12 @@ interface NewDeckReferenceStepProps {
61
72
  defaultDesignSystemId: string | null;
62
73
  defaultReferenceDeckId: string | null;
63
74
  recentReferences: RecentReference[];
64
- onSelect: (selection: NewDeckReferenceSelection) => void;
65
- onImport: (files: File[]) => Promise<void>;
66
- onSkip: () => void;
75
+ onSelect: (selection: NewDeckReferenceSelection) => void | Promise<void>;
76
+ onImport: (files: File[]) => Promise<ImportedReference | null>;
77
+ onImportSource: (
78
+ source: NewDeckReferenceSource,
79
+ ) => Promise<ImportedReference | null>;
80
+ onSkip: () => void | Promise<void>;
67
81
  onOpenChange: (open: boolean) => void;
68
82
  importing?: boolean;
69
83
  title: string;
@@ -72,7 +86,6 @@ interface NewDeckReferenceStepProps {
72
86
  chooseDeckLabel: string;
73
87
  importingLabel: string;
74
88
  skipLabel: string;
75
- defaultSuffix: string;
76
89
  starredLabel: string;
77
90
  otherDecksLabel: string;
78
91
  searchDecksLabel: string;
@@ -88,6 +101,7 @@ export function NewDeckReferenceStep({
88
101
  recentReferences,
89
102
  onSelect,
90
103
  onImport,
104
+ onImportSource,
91
105
  onSkip,
92
106
  onOpenChange,
93
107
  importing = false,
@@ -97,7 +111,6 @@ export function NewDeckReferenceStep({
97
111
  chooseDeckLabel,
98
112
  importingLabel,
99
113
  skipLabel,
100
- defaultSuffix,
101
114
  starredLabel,
102
115
  otherDecksLabel,
103
116
  searchDecksLabel,
@@ -110,6 +123,8 @@ export function NewDeckReferenceStep({
110
123
  const [selectedReferenceDeckId, setSelectedReferenceDeckId] = useState<
111
124
  string | null
112
125
  >(defaultReferenceDeckId);
126
+ const [importedReference, setImportedReference] =
127
+ useState<ImportedReference | null>(null);
113
128
  const [selectedSource, setSelectedSource] =
114
129
  useState<NewDeckReferenceSelection["referenceSource"]>(null);
115
130
  const [referenceDeckSearchOpen, setReferenceDeckSearchOpen] = useState(false);
@@ -141,9 +156,6 @@ export function NewDeckReferenceStep({
141
156
  );
142
157
  const starredDecks = decks.filter((deck) => deck.starred);
143
158
  const otherDecks = decks.filter((deck) => !deck.starred);
144
- const selectedDesignSystem = selectedDesignSystemId
145
- ? designSystemById.get(selectedDesignSystemId)
146
- : undefined;
147
159
  const selectedReferenceDeck = selectedReferenceDeckId
148
160
  ? deckById.get(selectedReferenceDeckId)
149
161
  : undefined;
@@ -152,6 +164,7 @@ export function NewDeckReferenceStep({
152
164
  if (!open) return;
153
165
  setSelectedDesignSystemId(defaultDesignSystemId);
154
166
  setSelectedReferenceDeckId(defaultReferenceDeckId);
167
+ setImportedReference(null);
155
168
  setSelectedSource(null);
156
169
  setReferenceDeckSearchOpen(false);
157
170
  }, [open, defaultDesignSystemId, defaultReferenceDeckId]);
@@ -160,17 +173,33 @@ export function NewDeckReferenceStep({
160
173
  const files = Array.from(event.target.files ?? []);
161
174
  event.target.value = "";
162
175
  if (files.length === 0) return;
163
- await onImport(files);
176
+ const imported = await onImport(files);
177
+ if (imported) applyImportedReference(imported);
164
178
  };
165
179
 
166
- const handleContinue = () => {
180
+ const applyImportedReference = (imported: ImportedReference) => {
181
+ setSelectedDesignSystemId(null);
182
+ setSelectedReferenceDeckId(imported.id);
183
+ setSelectedSource(null);
184
+ setImportedReference(imported);
185
+ };
186
+
187
+ const handleContinue = async () => {
188
+ const trimmedSource =
189
+ selectedSource && selectedSource.value.trim()
190
+ ? { ...selectedSource, value: selectedSource.value.trim() }
191
+ : null;
192
+
193
+ if (trimmedSource?.kind === "google-docs") {
194
+ const imported = await onImportSource(trimmedSource);
195
+ if (imported) applyImportedReference(imported);
196
+ return;
197
+ }
198
+
167
199
  onSelect({
168
200
  designSystemId: selectedDesignSystemId,
169
201
  referenceDeckId: selectedReferenceDeckId,
170
- referenceSource:
171
- selectedSource && selectedSource.value.trim()
172
- ? { ...selectedSource, value: selectedSource.value.trim() }
173
- : null,
202
+ referenceSource: trimmedSource,
174
203
  });
175
204
  };
176
205
 
@@ -182,23 +211,20 @@ export function NewDeckReferenceStep({
182
211
  setSelectedDesignSystemId(null);
183
212
  } else {
184
213
  setSelectedReferenceDeckId(null);
214
+ setImportedReference(null);
185
215
  }
186
216
  };
187
217
  const selectedSourceLabel =
188
218
  selectedSource?.kind === "google-docs"
189
- ? "Google Docs"
219
+ ? t("home.googleSlidesReferenceTitle")
190
220
  : selectedSource?.kind === "website"
191
221
  ? "Website"
192
222
  : "Figma";
193
- const selectedSourceIcon =
194
- selectedSource?.kind === "google-docs" ? (
195
- <IconBrandGoogle className="size-4" />
196
- ) : selectedSource?.kind === "website" ? (
197
- <IconWorld className="size-4" />
198
- ) : (
199
- <IconBrandFigma className="size-4" />
200
- );
201
-
223
+ const selectedReferenceDeckTitle =
224
+ selectedReferenceDeck?.title ??
225
+ (selectedReferenceDeckId === importedReference?.id
226
+ ? importedReference.title
227
+ : undefined);
202
228
  return open ? (
203
229
  <div
204
230
  className="fixed inset-0 z-[200] flex min-h-screen flex-col bg-background text-foreground"
@@ -228,29 +254,6 @@ export function NewDeckReferenceStep({
228
254
  </p>
229
255
  )}
230
256
 
231
- {(selectedDesignSystem || selectedSource) && (
232
- <div className="mt-8 space-y-2">
233
- {selectedDesignSystem && (
234
- <ReferenceRow
235
- icon={<IconPalette className="size-4" />}
236
- label={selectedDesignSystem.title}
237
- meta={`${designSystemLabel}${defaultDesignSystemId === selectedDesignSystem.id ? defaultSuffix : ""}`}
238
- selected
239
- onClick={() => setSelectedDesignSystemId(null)}
240
- />
241
- )}
242
- {selectedSource && (
243
- <ReferenceRow
244
- icon={selectedSourceIcon}
245
- label={selectedSourceLabel}
246
- meta={selectedSource.value || "Add a link below"}
247
- selected
248
- onClick={() => setSelectedSource(null)}
249
- />
250
- )}
251
- </div>
252
- )}
253
-
254
257
  <div className="mt-10 space-y-6">
255
258
  <div className="grid gap-2">
256
259
  <div className="flex items-center justify-between gap-3">
@@ -309,7 +312,7 @@ export function NewDeckReferenceStep({
309
312
  className="flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
310
313
  >
311
314
  <span className="truncate">
312
- {selectedReferenceDeck?.title ?? chooseDeckLabel}
315
+ {selectedReferenceDeckTitle ?? chooseDeckLabel}
313
316
  </span>
314
317
  <IconChevronDown className="size-4 shrink-0 opacity-50" />
315
318
  </button>
@@ -336,6 +339,7 @@ export function NewDeckReferenceStep({
336
339
  value={`${deck.title} ${deck.id}`}
337
340
  onSelect={() => {
338
341
  setSelectedReferenceDeckId(deck.id);
342
+ setImportedReference(null);
339
343
  setSelectedSource(null);
340
344
  setReferenceDeckSearchOpen(false);
341
345
  }}
@@ -361,6 +365,7 @@ export function NewDeckReferenceStep({
361
365
  value={`${deck.title} ${deck.id}`}
362
366
  onSelect={() => {
363
367
  setSelectedReferenceDeckId(deck.id);
368
+ setImportedReference(null);
364
369
  setSelectedSource(null);
365
370
  setReferenceDeckSearchOpen(false);
366
371
  }}
@@ -430,6 +435,8 @@ export function NewDeckReferenceStep({
430
435
  accept=".pptx"
431
436
  icon={<IconPresentation className="size-4" />}
432
437
  label="PPT"
438
+ imported={importedReference?.source === "pptx"}
439
+ importedLabel={t("home.imported")}
433
440
  importing={importing}
434
441
  importingLabel={importingLabel}
435
442
  onChange={handleImport}
@@ -438,6 +445,8 @@ export function NewDeckReferenceStep({
438
445
  accept=".pdf"
439
446
  icon={<IconFileTypePdf className="size-4" />}
440
447
  label="PDF"
448
+ imported={importedReference?.source === "pdf"}
449
+ importedLabel={t("home.imported")}
441
450
  importing={importing}
442
451
  importingLabel={importingLabel}
443
452
  onChange={handleImport}
@@ -446,25 +455,34 @@ export function NewDeckReferenceStep({
446
455
  accept=".docx"
447
456
  icon={<IconFileText className="size-4" />}
448
457
  label="DOCX"
458
+ imported={false}
459
+ importedLabel={t("home.imported")}
449
460
  importing={importing}
450
461
  importingLabel={importingLabel}
451
462
  onChange={handleImport}
452
463
  />
453
464
  <ImportOption
454
465
  icon={<IconBrandGoogle className="size-4" />}
455
- label="GDocs"
456
- selected={selectedSource?.kind === "google-docs"}
466
+ label={t("home.googleSlidesReferenceTitle")}
467
+ confirmed={importedReference?.source === "google-slides"}
468
+ confirmedLabel={t("home.imported")}
469
+ selected={
470
+ selectedSource?.kind === "google-docs" ||
471
+ importedReference?.source === "google-slides"
472
+ }
457
473
  onClick={() => chooseSource("google-docs")}
458
474
  />
459
475
  <ImportOption
460
476
  icon={<IconWorld className="size-4" />}
461
477
  label="Website"
478
+ confirmedLabel={t("home.imported")}
462
479
  selected={selectedSource?.kind === "website"}
463
480
  onClick={() => chooseSource("website")}
464
481
  />
465
482
  <ImportOption
466
483
  icon={<IconBrandFigma className="size-4" />}
467
484
  label="Figma"
485
+ confirmedLabel={t("home.imported")}
468
486
  selected={selectedSource?.kind === "figma"}
469
487
  onClick={() => chooseSource("figma")}
470
488
  />
@@ -484,23 +502,54 @@ export function NewDeckReferenceStep({
484
502
  }
485
503
  />
486
504
  )}
505
+ {importedReference && (
506
+ <div
507
+ className="mt-3 flex items-start gap-3 rounded-lg border border-primary/30 bg-primary/5 px-3 py-2.5 text-sm"
508
+ role="status"
509
+ aria-live="polite"
510
+ >
511
+ <span className="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
512
+ <IconCheck className="size-3.5" />
513
+ </span>
514
+ <div className="min-w-0">
515
+ <p className="font-medium text-foreground">
516
+ {t("home.referenceImportSuccess")}
517
+ </p>
518
+ <p className="truncate text-xs text-muted-foreground">
519
+ {t("home.referenceImportSelected", {
520
+ title: importedReference.title,
521
+ })}
522
+ </p>
523
+ </div>
524
+ </div>
525
+ )}
487
526
  </div>
488
527
  </div>
489
528
  </div>
490
529
  </main>
491
530
 
492
531
  <footer className="flex shrink-0 items-center justify-between border-t border-border px-5 py-4 sm:px-8">
493
- <Button type="button" variant="ghost" onClick={onSkip}>
532
+ <Button
533
+ type="button"
534
+ variant="ghost"
535
+ onClick={onSkip}
536
+ disabled={importing}
537
+ >
494
538
  {skipLabel}
495
539
  </Button>
496
540
  <Button
497
541
  type="button"
498
- onClick={handleContinue}
542
+ onClick={() => void handleContinue()}
543
+ aria-busy={importing}
499
544
  disabled={
500
545
  importing || Boolean(selectedSource && !selectedSource.value.trim())
501
546
  }
502
547
  >
503
- Continue
548
+ {importing
549
+ ? importingLabel
550
+ : importedReference
551
+ ? t("home.continueToGenerate")
552
+ : t("home.continue")}
504
553
  <IconCheck className="ms-1.5 size-4" />
505
554
  </Button>
506
555
  </footer>
@@ -512,6 +561,8 @@ function FileImportOption({
512
561
  accept,
513
562
  icon,
514
563
  label,
564
+ imported = false,
565
+ importedLabel,
515
566
  importing,
516
567
  importingLabel,
517
568
  onChange,
@@ -519,6 +570,8 @@ function FileImportOption({
519
570
  accept: string;
520
571
  icon: ReactNode;
521
572
  label: string;
573
+ imported?: boolean;
574
+ importedLabel: string;
522
575
  importing: boolean;
523
576
  importingLabel: string;
524
577
  onChange: (event: ChangeEvent<HTMLInputElement>) => void;
@@ -529,8 +582,9 @@ function FileImportOption({
529
582
  "flex cursor-pointer items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors hover:bg-accent",
530
583
  importing && "pointer-events-none opacity-60",
531
584
  )}
585
+ aria-label={imported ? `${label} - ${importedLabel}` : label}
532
586
  >
533
- {icon}
587
+ {imported ? <IconCheck className="size-4 text-primary" /> : icon}
534
588
  <span>{importing ? importingLabel : label}</span>
535
589
  <input
536
590
  type="file"
@@ -547,11 +601,15 @@ function FileImportOption({
547
601
  function ImportOption({
548
602
  icon,
549
603
  label,
604
+ confirmed = false,
605
+ confirmedLabel,
550
606
  selected = false,
551
607
  onClick,
552
608
  }: {
553
609
  icon: ReactNode;
554
610
  label: string;
611
+ confirmed?: boolean;
612
+ confirmedLabel?: string;
555
613
  selected?: boolean;
556
614
  onClick: () => void;
557
615
  }) {
@@ -565,9 +623,12 @@ function ImportOption({
565
623
  ? "border-primary/50 bg-primary/5 text-primary"
566
624
  : "border-border hover:bg-accent",
567
625
  )}
626
+ aria-label={
627
+ confirmed && confirmedLabel ? `${label} - ${confirmedLabel}` : label
628
+ }
568
629
  aria-pressed={selected}
569
630
  >
570
- {icon}
631
+ {confirmed ? <IconCheck className="size-4 text-primary" /> : icon}
571
632
  <span>{label}</span>
572
633
  </button>
573
634
  );
@@ -4328,6 +4328,7 @@ export default function SlideEditor({
4328
4328
  edits={activeSlideEdits}
4329
4329
  resolveRect={resolveCanvasRect}
4330
4330
  containerRef={slideCanvasRef}
4331
+ outlineOnly
4331
4332
  />
4332
4333
  )}
4333
4334
  {(agentActive || passivePresentUsers.length > 0) && (
@@ -9,7 +9,9 @@ import { toast } from "sonner";
9
9
 
10
10
  import {
11
11
  NewDeckReferenceStep,
12
+ type ImportedReference,
12
13
  type NewDeckReferenceSelection,
14
+ type NewDeckReferenceSource,
13
15
  } from "@/components/editor/NewDeckReferenceStep";
14
16
  import {
15
17
  uploadPromptFiles,
@@ -204,37 +206,159 @@ export function FirstDeckOnboardingFlow({
204
206
  );
205
207
 
206
208
  const handleReferenceImport = useCallback(
207
- async (files: File[]) => {
209
+ async (files: File[]): Promise<ImportedReference | null> => {
208
210
  setReferenceImporting(true);
209
211
  try {
210
212
  const uploaded = await uploadPromptFiles(files);
211
213
  const pptxReference = uploaded.find((file) =>
212
214
  file.originalName.toLowerCase().endsWith(".pptx"),
213
215
  );
214
- let referenceSelection: NewDeckReferenceSelection = {
215
- designSystemId: null,
216
- referenceDeckId: null,
217
- };
216
+ const pdfReference = uploaded.find((file) =>
217
+ file.originalName.toLowerCase().endsWith(".pdf"),
218
+ );
219
+ let importedReference: ImportedReference | null = null;
218
220
  let generationFiles = uploaded;
219
221
  if (pptxReference) {
220
222
  const imported = (await callAction("import-pptx", {
221
223
  filePath: pptxReference.path,
222
- })) as { id?: unknown };
223
- if (typeof imported.id !== "string" || !imported.id) {
224
+ })) as {
225
+ id?: unknown;
226
+ imported?: unknown;
227
+ slideCount?: unknown;
228
+ title?: unknown;
229
+ };
230
+ if (
231
+ typeof imported.id !== "string" ||
232
+ !imported.id ||
233
+ imported.imported !== true ||
234
+ typeof imported.slideCount !== "number" ||
235
+ imported.slideCount < 1
236
+ ) {
224
237
  throw new Error("The imported presentation did not create a deck.");
225
238
  }
226
- await reloadDecks();
227
- rememberReference({ id: imported.id, kind: "deck" });
228
- referenceSelection = {
229
- designSystemId: null,
230
- referenceDeckId: imported.id,
239
+ importedReference = {
240
+ id: imported.id,
241
+ title:
242
+ typeof imported.title === "string" && imported.title
243
+ ? imported.title
244
+ : t("home.importedReferenceDeck"),
245
+ source: "pptx",
231
246
  };
232
247
  generationFiles = uploaded.filter((file) => file !== pptxReference);
248
+ } else if (pdfReference) {
249
+ const referenceDeck = createDeck(undefined, {
250
+ noDefaultSlides: true,
251
+ });
252
+ const persisted = await ensureDeckPersisted(referenceDeck.id);
253
+ if (!persisted.persisted) {
254
+ deleteDeck(referenceDeck.id);
255
+ throw new Error(
256
+ describeDeckPersistenceFailure(
257
+ persisted,
258
+ "The PDF reference deck could not be saved.",
259
+ ),
260
+ );
261
+ }
262
+ try {
263
+ const imported = (await callAction("import-file", {
264
+ filePath: pdfReference.path,
265
+ format: "pdf",
266
+ deckId: referenceDeck.id,
267
+ importIntoDeck: true,
268
+ })) as {
269
+ imported?: unknown;
270
+ deckId?: unknown;
271
+ pageCount?: unknown;
272
+ title?: unknown;
273
+ };
274
+ if (
275
+ imported.imported !== true ||
276
+ imported.deckId !== referenceDeck.id ||
277
+ typeof imported.pageCount !== "number" ||
278
+ imported.pageCount < 1
279
+ ) {
280
+ throw new Error("The PDF reference deck could not be imported.");
281
+ }
282
+ importedReference = {
283
+ id: referenceDeck.id,
284
+ title:
285
+ typeof imported.title === "string" && imported.title
286
+ ? imported.title
287
+ : t("home.importedReferenceDeck"),
288
+ source: "pdf",
289
+ };
290
+ generationFiles = uploaded.filter((file) => file !== pdfReference);
291
+ } catch (error) {
292
+ deleteDeck(referenceDeck.id);
293
+ throw error;
294
+ }
233
295
  }
234
- await startGeneration(
235
- [...promptFiles, ...generationFiles],
236
- referenceSelection,
237
- );
296
+ setPromptFiles((current) => [...current, ...generationFiles]);
297
+ if (importedReference) {
298
+ await reloadDecks();
299
+ rememberReference({ id: importedReference.id, kind: "deck" });
300
+ }
301
+ return importedReference;
302
+ } catch (error) {
303
+ toast.error(t("editorToolbar.uploadFailed"), {
304
+ description:
305
+ error instanceof Error
306
+ ? error.message
307
+ : t("editorToolbar.importFailedDescription"),
308
+ });
309
+ return null;
310
+ } finally {
311
+ setReferenceImporting(false);
312
+ }
313
+ },
314
+ [
315
+ callAction,
316
+ createDeck,
317
+ deleteDeck,
318
+ ensureDeckPersisted,
319
+ reloadDecks,
320
+ rememberReference,
321
+ t,
322
+ ],
323
+ );
324
+
325
+ const handleReferenceSourceImport = useCallback(
326
+ async (
327
+ source: NewDeckReferenceSource,
328
+ ): Promise<ImportedReference | null> => {
329
+ if (source.kind !== "google-docs") return null;
330
+ setReferenceImporting(true);
331
+ try {
332
+ const imported = (await callAction("import-google-slides-reference", {
333
+ presentationUrl: source.value,
334
+ })) as {
335
+ id?: unknown;
336
+ imported?: unknown;
337
+ slideCount?: unknown;
338
+ title?: unknown;
339
+ };
340
+ if (
341
+ typeof imported.id !== "string" ||
342
+ !imported.id ||
343
+ imported.imported !== true ||
344
+ typeof imported.slideCount !== "number" ||
345
+ imported.slideCount < 1
346
+ ) {
347
+ throw new Error(
348
+ "The Google Slides presentation did not create a deck.",
349
+ );
350
+ }
351
+ const importedReference: ImportedReference = {
352
+ id: imported.id,
353
+ title:
354
+ typeof imported.title === "string" && imported.title
355
+ ? imported.title
356
+ : t("home.importedReferenceDeck"),
357
+ source: "google-slides",
358
+ };
359
+ await reloadDecks();
360
+ rememberReference({ id: importedReference.id, kind: "deck" });
361
+ return importedReference;
238
362
  } catch (error) {
239
363
  toast.error(t("editorToolbar.uploadFailed"), {
240
364
  description:
@@ -242,11 +366,12 @@ export function FirstDeckOnboardingFlow({
242
366
  ? error.message
243
367
  : t("editorToolbar.importFailedDescription"),
244
368
  });
369
+ return null;
245
370
  } finally {
246
371
  setReferenceImporting(false);
247
372
  }
248
373
  },
249
- [promptFiles, reloadDecks, rememberReference, startGeneration, t],
374
+ [callAction, reloadDecks, rememberReference, t],
250
375
  );
251
376
 
252
377
  const handleReferenceSkip = useCallback(() => {
@@ -267,6 +392,7 @@ export function FirstDeckOnboardingFlow({
267
392
  recentReferences={recentReferences}
268
393
  onSelect={handleReferenceSelect}
269
394
  onImport={handleReferenceImport}
395
+ onImportSource={handleReferenceSourceImport}
270
396
  onSkip={handleReferenceSkip}
271
397
  onOpenChange={(open) => {
272
398
  if (!open) setStep("prompt");
@@ -278,7 +404,6 @@ export function FirstDeckOnboardingFlow({
278
404
  chooseDeckLabel={t("home.referenceDeckPlaceholder")}
279
405
  importingLabel={t("raw.uploading")}
280
406
  skipLabel={t("home.referenceDeckNone")}
281
- defaultSuffix={t("home.defaultSuffix")}
282
407
  starredLabel={t("home.referenceDeckStarredGroup")}
283
408
  otherDecksLabel={t("home.referenceDeckOtherGroup")}
284
409
  searchDecksLabel={t("root.searchDecks")}
@@ -672,6 +672,12 @@ const messages = {
672
672
  chooseReferences: "Choose references",
673
673
  addDesignSystem: "+ Design system",
674
674
  importFrom: "Import from",
675
+ imported: "Imported",
676
+ importedReferenceDeck: "Imported reference deck",
677
+ referenceImportSuccess: "Imported successfully",
678
+ referenceImportSelected: '"{{title}}" is now the reference deck.',
679
+ continue: "Continue",
680
+ continueToGenerate: "Continue to generate",
675
681
  designSystem: "Design system",
676
682
  referenceDeck: "Reference deck",
677
683
  referenceDeckPlaceholder: "Match the style of an existing deck",
@@ -2,12 +2,18 @@ export function shouldShowNewDeckGeneratingOverlay({
2
2
  generating,
3
3
  isNewDeckCreation,
4
4
  slideCount,
5
+ generationStarted,
5
6
  }: {
6
7
  generating: boolean;
7
8
  isNewDeckCreation: boolean;
8
9
  slideCount?: number | null;
10
+ generationStarted: boolean;
9
11
  }): boolean {
10
- return generating && isNewDeckCreation && (slideCount ?? 0) === 0;
12
+ return (
13
+ isNewDeckCreation &&
14
+ (slideCount ?? 0) === 0 &&
15
+ (generating || !generationStarted)
16
+ );
11
17
  }
12
18
 
13
19
  export function shouldShowNewDeckGeneratingProgress({