@agent-native/core 0.136.5 → 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 (174) 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/clips/app/components/import-menu.tsx +109 -0
  10. package/corpus/templates/clips/app/components/library/library-grid.tsx +8 -18
  11. package/corpus/templates/clips/app/components/library/library-layout.tsx +14 -50
  12. package/corpus/templates/clips/app/components/library/sort-menu.tsx +20 -6
  13. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +10 -34
  14. package/corpus/templates/clips/app/routes/_app.library._index.tsx +0 -2
  15. package/corpus/templates/clips/changelog/2026-08-04-cleaner-library-controls-with-an-icon-only-sort-button.md +6 -0
  16. package/corpus/templates/clips/changelog/2026-08-04-upload-and-loom-imports-now-live-under-one-import-menu.md +6 -0
  17. package/corpus/templates/content/actions/_database-utils.ts +6 -0
  18. package/corpus/templates/content/actions/bind-content-database-source-field.ts +273 -97
  19. package/corpus/templates/content/actions/change-content-database-source-role.ts +65 -29
  20. package/corpus/templates/content/actions/configure-document-property.ts +113 -32
  21. package/corpus/templates/content/actions/delete-content-database.ts +5 -7
  22. package/corpus/templates/content/actions/delete-document-property.ts +109 -62
  23. package/corpus/templates/content/actions/delete-document.ts +178 -36
  24. package/corpus/templates/content/actions/duplicate-database-item.ts +27 -0
  25. package/corpus/templates/content/actions/duplicate-database-items.ts +31 -1
  26. package/corpus/templates/content/actions/permanently-delete-document.ts +4 -6
  27. package/corpus/templates/content/actions/remove-database-items.ts +13 -0
  28. package/corpus/templates/content/actions/set-document-property.ts +69 -1
  29. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +814 -0
  30. package/corpus/templates/content/parity/matrix.md +1 -1
  31. package/corpus/templates/content/parity/matrix.ts +1 -0
  32. package/corpus/templates/content/server/db/schema.ts +27 -0
  33. package/corpus/templates/content/server/plugins/db.ts +24 -0
  34. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +122 -114
  35. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
  36. package/corpus/templates/design/app/components/design/EditPanel.tsx +68 -34
  37. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +78 -4
  38. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +8 -0
  39. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +5 -2
  40. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +10 -9
  41. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +31 -69
  42. package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +80 -106
  43. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  44. package/corpus/templates/design/app/hooks/use-editor-preferences.ts +75 -0
  45. package/corpus/templates/design/app/i18n-data.ts +8 -0
  46. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +61 -0
  47. package/corpus/templates/design/app/pages/design-editor/editor-preferences.ts +98 -0
  48. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +491 -0
  49. package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +133 -0
  50. package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +8 -0
  51. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -12
  52. package/corpus/templates/design/changelog/2026-08-03-arrow-keys-now-reorder-a-layer-inside-an-auto-layout-frame.md +6 -0
  53. package/corpus/templates/design/changelog/2026-08-03-auto-layout-flow-icons-show-the-direction-at-a-glance-instea.md +6 -0
  54. package/corpus/templates/design/changelog/2026-08-03-auto-layout-icons-share-one-outlined-square-style-so-flow-ga.md +6 -0
  55. package/corpus/templates/design/changelog/2026-08-03-export-has-a-named-preview-section-you-can-expand-replacing-.md +6 -0
  56. package/corpus/templates/design/changelog/2026-08-03-export-preview-shows-the-selected-element-instead-of-the-who.md +6 -0
  57. package/corpus/templates/design/changelog/2026-08-03-inspector-sections-show-an-expand-indicator-so-it-is-clear-w.md +6 -0
  58. package/corpus/templates/design/changelog/2026-08-03-paste-lands-inside-a-selected-frame-instead-of-beside-it.md +6 -0
  59. package/corpus/templates/design/changelog/2026-08-03-the-add-breakpoint-button-sits-outside-the-breakpoint-select.md +6 -0
  60. package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-keep-space-below-the-continu.md +6 -0
  61. package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-share-the-chat-panel-s-backg.md +6 -0
  62. package/corpus/templates/design/changelog/2026-08-03-the-export-panel-no-longer-stacks-a-duplicate-export-block-o.md +6 -0
  63. package/corpus/templates/design/changelog/2026-08-03-the-text-tool-uses-a-t-icon-matching-the-letter-it-inserts.md +6 -0
  64. package/corpus/templates/factory/.env.example +17 -0
  65. package/corpus/templates/factory/AGENTS.md +27 -17
  66. package/corpus/templates/factory/README.md +19 -9
  67. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +90 -0
  68. package/corpus/templates/factory/actions/get-triage-config.ts +14 -3
  69. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +386 -0
  70. package/corpus/templates/factory/actions/list-factory-automations.ts +53 -0
  71. package/corpus/templates/factory/actions/list-triage-items.ts +7 -2
  72. package/corpus/templates/factory/actions/poll-github-sources.ts +203 -0
  73. package/corpus/templates/factory/actions/poll-sentry-errors.ts +161 -0
  74. package/corpus/templates/factory/actions/poll-slack-channel.ts +4 -2
  75. package/corpus/templates/factory/actions/run-factory-automation.ts +42 -0
  76. package/corpus/templates/factory/actions/save-factory-automation.ts +69 -0
  77. package/corpus/templates/factory/actions/save-triage-config.ts +28 -2
  78. package/corpus/templates/factory/actions/start-builder-for-item.ts +411 -0
  79. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +14 -3
  80. package/corpus/templates/factory/app/components/ui/checkbox.tsx +1 -0
  81. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +7 -0
  82. package/corpus/templates/factory/app/i18n/en-US.ts +35 -1
  83. package/corpus/templates/factory/app/routes/factory.tsx +462 -9
  84. package/corpus/templates/factory/changelog/2026-08-04-added-organization-owned-factory-automations-for-slack-bug-t.md +6 -0
  85. package/corpus/templates/factory/changelog/2026-08-04-automation-editor-controls-and-model-picker.md +6 -0
  86. package/corpus/templates/factory/netlify.toml +2 -2
  87. package/corpus/templates/factory/server/connectors/credentials.ts +6 -5
  88. package/corpus/templates/factory/server/connectors/slack.ts +133 -5
  89. package/corpus/templates/factory/server/db/schema.ts +6 -0
  90. package/corpus/templates/factory/server/lib/require-factory-automation.ts +65 -0
  91. package/corpus/templates/factory/server/plugins/agent-chat.ts +29 -11
  92. package/corpus/templates/factory/server/plugins/auth.ts +1 -0
  93. package/corpus/templates/factory/server/plugins/factory-migrations.ts +12 -0
  94. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +218 -67
  95. package/corpus/templates/factory/server/triage/builder-executor.ts +4 -0
  96. package/corpus/templates/factory/server/triage/contracts.ts +14 -1
  97. package/corpus/templates/factory/server/triage/github-client.ts +404 -0
  98. package/corpus/templates/factory/server/triage/metadata.ts +44 -0
  99. package/corpus/templates/factory/server/triage/pr-babysit.ts +5 -5
  100. package/corpus/templates/factory/server/triage/pr-policy.ts +144 -0
  101. package/corpus/templates/factory/server/triage/sentry-client.ts +180 -0
  102. package/corpus/templates/factory/server/triage/slack-client.ts +43 -0
  103. package/corpus/templates/factory/server/triage/slack-poller.ts +9 -2
  104. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +27 -0
  105. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +14 -4
  106. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -0
  107. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -0
  108. package/corpus/templates/plan/actions/create-plan-design.ts +8 -6
  109. package/corpus/templates/plan/actions/create-prototype-plan.ts +8 -6
  110. package/corpus/templates/plan/actions/create-ui-plan.ts +7 -7
  111. package/corpus/templates/plan/actions/get-plan-blocks.ts +7 -1
  112. package/corpus/templates/plan/actions/validate-plan-input.ts +39 -0
  113. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +167 -11
  114. package/corpus/templates/plan/app/i18n/en-US.ts +1 -0
  115. package/corpus/templates/plan/changelog/2026-08-04-canvas-zoom-controls-now-explain-the-command-ctrl-plus-scrol.md +6 -0
  116. package/corpus/templates/plan/changelog/2026-08-04-pinch-to-zoom-is-now-supported-on-touchscreens-in-visual-pla.md +6 -0
  117. package/corpus/templates/plan/shared/plan-content.ts +17 -2
  118. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +321 -0
  119. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +32 -11
  120. package/corpus/templates/slides/app/lib/recent-references.ts +82 -0
  121. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  122. package/corpus/templates/slides/app/pages/Index.tsx +236 -181
  123. package/dist/cli/skills-content/canvas.d.ts +3 -3
  124. package/dist/cli/skills-content/canvas.js +16 -5
  125. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  126. package/dist/cli/skills-content/visual-plan-skill.js +27 -0
  127. package/dist/cli/skills-content/wireframe.d.ts +2 -2
  128. package/dist/cli/skills-content/wireframe.js +7 -0
  129. package/dist/client/AgentPanel.js +23 -8
  130. package/dist/client/DefaultSpinner.d.ts +0 -12
  131. package/dist/client/DefaultSpinner.js +22 -2
  132. package/dist/client/api-path.d.ts +6 -0
  133. package/dist/client/api-path.js +55 -0
  134. package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -0
  135. package/dist/client/onboarding/FirstRunOnboarding.js +93 -0
  136. package/dist/client/onboarding/first-run-enabled.d.ts +2 -0
  137. package/dist/client/onboarding/first-run-enabled.js +9 -0
  138. package/dist/client/onboarding/index.d.ts +2 -1
  139. package/dist/client/onboarding/index.js +1 -0
  140. package/dist/client/onboarding/use-onboarding.d.ts +6 -1
  141. package/dist/client/onboarding/use-onboarding.js +35 -1
  142. package/dist/client/route-state.js +10 -0
  143. package/dist/collab/awareness.d.ts +2 -2
  144. package/dist/collab/routes.d.ts +1 -1
  145. package/dist/connections/catalog.d.ts +4 -4
  146. package/dist/connections/catalog.js +3 -3
  147. package/dist/db/index.d.ts +1 -1
  148. package/dist/db/index.js +1 -1
  149. package/dist/deploy/build.js +2 -1
  150. package/dist/notifications/routes.d.ts +3 -3
  151. package/dist/observability/routes.d.ts +1 -1
  152. package/dist/onboarding/app-profile.d.ts +4 -0
  153. package/dist/onboarding/app-profile.js +378 -0
  154. package/dist/onboarding/index.d.ts +2 -1
  155. package/dist/onboarding/index.js +1 -0
  156. package/dist/onboarding/plugin.d.ts +2 -0
  157. package/dist/onboarding/plugin.js +21 -1
  158. package/dist/onboarding/types.d.ts +19 -0
  159. package/dist/progress/routes.d.ts +1 -1
  160. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  161. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  162. package/dist/secrets/routes.d.ts +3 -3
  163. package/dist/server/auth.js +21 -1
  164. package/dist/server/better-auth-instance.d.ts +3 -2
  165. package/dist/server/better-auth-instance.js +9 -7
  166. package/dist/server/email-markdown.js +3 -8
  167. package/dist/shared/first-run-onboarding.d.ts +3 -0
  168. package/dist/shared/first-run-onboarding.js +3 -0
  169. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
  170. package/dist/triggers/index.d.ts +3 -0
  171. package/dist/triggers/index.js +5 -0
  172. package/docs/content/cross-app-sso.mdx +26 -0
  173. package/package.json +1 -1
  174. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
@@ -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.",
@@ -1,4 +1,3 @@
1
- import { askUserQuestion } from "@agent-native/core/client/agent-chat";
2
1
  import { callAction, useSession } from "@agent-native/core/client/hooks";
3
2
  import { useT } from "@agent-native/core/client/i18n";
4
3
  import { buildSignInReturnHref } from "@agent-native/core/client/ui";
@@ -21,8 +20,14 @@ import { useLocation, useNavigate, useSearchParams } from "react-router";
21
20
  import { toast } from "sonner";
22
21
 
23
22
  import DeckCard from "@/components/deck/DeckCard";
24
- import PromptPopover from "@/components/editor/PromptDialog";
25
- import type { UploadedFile } from "@/components/editor/PromptDialog";
23
+ import {
24
+ NewDeckReferenceStep,
25
+ type NewDeckReferenceSelection,
26
+ } from "@/components/editor/NewDeckReferenceStep";
27
+ import PromptPopover, {
28
+ uploadPromptFiles,
29
+ type UploadedFile,
30
+ } from "@/components/editor/PromptDialog";
26
31
  import {
27
32
  AlertDialog,
28
33
  AlertDialogAction,
@@ -34,15 +39,6 @@ import {
34
39
  AlertDialogTitle,
35
40
  } from "@/components/ui/alert-dialog";
36
41
  import { Button } from "@/components/ui/button";
37
- import {
38
- Select,
39
- SelectContent,
40
- SelectGroup,
41
- SelectItem,
42
- SelectLabel,
43
- SelectTrigger,
44
- SelectValue,
45
- } from "@/components/ui/select";
46
42
  import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
47
43
  import type { Deck } from "@/context/DeckContext";
48
44
  import { useDecks } from "@/context/DeckContext";
@@ -51,7 +47,11 @@ import { useDesignSystems } from "@/hooks/use-design-systems";
51
47
  import { useWorkspaceDefaults } from "@/hooks/use-workspace-defaults";
52
48
  import { createDeckAgentMessage } from "@/lib/agent-visible-message";
53
49
  import { savePromptToComposerDraft } from "@/lib/composer-draft";
54
- import { cn } from "@/lib/utils";
50
+ import {
51
+ readRecentReferences,
52
+ rememberRecentReference,
53
+ type RecentReference,
54
+ } from "@/lib/recent-references";
55
55
 
56
56
  const NEW_DECK_DRAFT_SCOPE = "slides-new-deck";
57
57
  const PENDING_PROMPT_KEY = "slides:pending-deck-prompt";
@@ -233,6 +233,17 @@ export default function Index() {
233
233
  const [newDeckRetryFiles, setNewDeckRetryFiles] = useState<UploadedFile[]>(
234
234
  [],
235
235
  );
236
+ const [pendingDeck, setPendingDeck] = useState<{
237
+ prompt: string;
238
+ files: UploadedFile[];
239
+ } | null>(null);
240
+ const [showNewDeckReferenceStep, setShowNewDeckReferenceStep] =
241
+ useState(false);
242
+ const [recentReferences, setRecentReferences] = useState<RecentReference[]>(
243
+ [],
244
+ );
245
+ const [referenceImporting, setReferenceImporting] = useState(false);
246
+ const initialPromptConsumedRef = useRef(false);
236
247
  const [signInPromptHadFiles, setSignInPromptHadFiles] = useState(false);
237
248
  const [selectedDesignSystemId, setSelectedDesignSystemId] = useState("");
238
249
  const [selectedReferenceDeckId, setSelectedReferenceDeckId] = useState("");
@@ -254,14 +265,6 @@ export default function Index() {
254
265
  () => new Map(designSystems.map((ds) => [ds.id, ds.title])),
255
266
  [designSystems],
256
267
  );
257
- const starredDecks = useMemo(
258
- () => decks.filter((deck) => deck.starred),
259
- [decks],
260
- );
261
- const unstarredDecks = useMemo(
262
- () => decks.filter((deck) => !deck.starred),
263
- [decks],
264
- );
265
268
  // A workspace default the caller cannot open is reported by the action as
266
269
  // `unavailable` rather than absent; preselecting it would send every new
267
270
  // prompt at a deck that 404s, so fall back to no reference instead.
@@ -289,6 +292,67 @@ export default function Index() {
289
292
  deckFilter === "mine" ? decks.filter((deck) => deck.createdByMe) : decks,
290
293
  [deckFilter, decks],
291
294
  );
295
+ const rememberReference = useCallback(
296
+ (reference: Omit<RecentReference, "lastUsedAt">) => {
297
+ const result = rememberRecentReference(reference);
298
+ if (result.readable) setRecentReferences(result.items);
299
+ },
300
+ [],
301
+ );
302
+
303
+ useEffect(() => {
304
+ const result = readRecentReferences();
305
+ if (result.readable) setRecentReferences(result.items);
306
+ }, []);
307
+
308
+ useEffect(() => {
309
+ if (initialDesignSystemId) {
310
+ rememberReference({
311
+ id: initialDesignSystemId,
312
+ kind: "design-system",
313
+ });
314
+ }
315
+ if (workspaceReferenceDeckId) {
316
+ rememberReference({ id: workspaceReferenceDeckId, kind: "deck" });
317
+ }
318
+ }, [initialDesignSystemId, rememberReference, workspaceReferenceDeckId]);
319
+
320
+ const initialPrompt = searchParams.get("initialPrompt")?.trim() ?? "";
321
+ const openInitialPromptReferenceStep = useCallback(() => {
322
+ if (!initialPrompt || initialPromptConsumedRef.current) return;
323
+ initialPromptConsumedRef.current = true;
324
+ setPendingDeck({ prompt: initialPrompt, files: [] });
325
+ setShowNewDeckReferenceStep(true);
326
+ setSearchParams(
327
+ (previous) => {
328
+ const next = new URLSearchParams(previous);
329
+ next.delete("initialPrompt");
330
+ return next;
331
+ },
332
+ { replace: true },
333
+ );
334
+ }, [initialPrompt, setSearchParams]);
335
+
336
+ useEffect(() => {
337
+ if (!initialPrompt || initialPromptConsumedRef.current) return;
338
+ const firstRunActive =
339
+ typeof document !== "undefined" &&
340
+ document.cookie
341
+ .split(";")
342
+ .some((part) => part.trim().startsWith("agent-native-first-run="));
343
+ const handleFirstRunCompleted = () => openInitialPromptReferenceStep();
344
+ window.addEventListener(
345
+ "agent-native:first-run-completed",
346
+ handleFirstRunCompleted,
347
+ );
348
+ if (!firstRunActive) openInitialPromptReferenceStep();
349
+ return () =>
350
+ window.removeEventListener(
351
+ "agent-native:first-run-completed",
352
+ handleFirstRunCompleted,
353
+ );
354
+ }, [initialPrompt, openInitialPromptReferenceStep]);
355
+
292
356
  const setDeckFilter = useCallback(
293
357
  (value: string) => {
294
358
  const nextFilter = value === "mine" ? "mine" : "all";
@@ -443,6 +507,7 @@ export default function Index() {
443
507
  const handleCreateDeckWithPrompt = async (
444
508
  prompt: string,
445
509
  files: UploadedFile[],
510
+ referenceSelection: NewDeckReferenceSelection = {},
446
511
  ) => {
447
512
  // Pre-flight auth check. The add-deck action returns 403 silently
448
513
  // when unauthenticated, leaving the user stuck on a deck page that
@@ -458,10 +523,21 @@ export default function Index() {
458
523
  newDeckRetryFiles,
459
524
  files,
460
525
  );
461
- const selectedDesignSystem =
462
- selectedDesignSystemId && selectedDesignSystemId !== "none"
463
- ? designSystems.find((ds) => ds.id === selectedDesignSystemId)
464
- : undefined;
526
+ const designSystemId =
527
+ referenceSelection.designSystemId !== undefined
528
+ ? referenceSelection.designSystemId
529
+ : selectedDesignSystemId && selectedDesignSystemId !== "none"
530
+ ? selectedDesignSystemId
531
+ : null;
532
+ const referenceDeckId =
533
+ referenceSelection.referenceDeckId !== undefined
534
+ ? referenceSelection.referenceDeckId
535
+ : selectedReferenceDeckId && selectedReferenceDeckId !== "none"
536
+ ? selectedReferenceDeckId
537
+ : null;
538
+ const selectedDesignSystem = designSystemId
539
+ ? designSystems.find((ds) => ds.id === designSystemId)
540
+ : undefined;
465
541
  let deck: ReturnType<typeof createDeck> | undefined;
466
542
  flushSync(() => {
467
543
  deck = createDeck(undefined, {
@@ -492,56 +568,6 @@ export default function Index() {
492
568
  setNewDeckRetryFiles([]);
493
569
  navigate(`/deck/${deck.id}`, { flushSync: true });
494
570
 
495
- // The deck now exists server-side and the user is looking at it, so a
496
- // failure past this point (e.g. askUserQuestion rejecting on an
497
- // application-state write error) must undo the navigation and restore
498
- // the prompt instead of stranding the user on a permanently empty deck.
499
- const recoverFromGenerationSetupFailure = () => {
500
- deleteDeck(deckId);
501
- toast.error(t("home.generationStartFailed"), {
502
- description: t("home.generationStartFailedDescription"),
503
- });
504
- // This Index instance is about to unmount (we already navigated away
505
- // to the deck page above), so state setters here would land on a dead
506
- // component. Hand the retry payload to the next Index mount via router
507
- // state instead; the effect above restores it.
508
- const retryState: DeckGenerationRetryState = {
509
- retryPrompt: prompt,
510
- retryFiles: filesForGeneration,
511
- };
512
- navigate("/", { state: retryState });
513
- };
514
-
515
- let deckLength: Awaited<ReturnType<typeof askUserQuestion>> = null;
516
- try {
517
- // One quick, skippable decision so the agent doesn't guess the deck size.
518
- deckLength = await askUserQuestion({
519
- question: t("home.deckLengthQuestion"),
520
- header: t("home.deckLengthHeader"),
521
- options: [
522
- { label: t("home.deckLengthShort"), value: "3–5 slides" },
523
- {
524
- label: t("home.deckLengthMedium"),
525
- value: "6–10 slides",
526
- recommended: true,
527
- },
528
- { label: t("home.deckLengthLong"), value: "11+ slides" },
529
- {
530
- label: t("home.deckLengthSingleVisual"),
531
- value: "a single standalone visual slide",
532
- },
533
- ],
534
- allowFreeText: false,
535
- });
536
- } catch {
537
- recoverFromGenerationSetupFailure();
538
- return;
539
- }
540
- const deckLengthContext =
541
- typeof deckLength === "string" && deckLength
542
- ? `Target length: aim for ${deckLength} unless the user's request clearly specifies a different count.`
543
- : "";
544
-
545
571
  const trimmedPrompt = prompt.trim();
546
572
  const hasImportedGoogleDocContext = trimmedPrompt.includes("<google-doc ");
547
573
  const googleDocUrls = hasImportedGoogleDocContext
@@ -561,11 +587,8 @@ export default function Index() {
561
587
  "If the action cannot read a private document, tell the user the exact sharing step from the action error instead of generating from the URL alone.",
562
588
  ].join("\n")
563
589
  : "";
564
- const referenceDeckContext = await loadReferenceDeckGenerationContext(
565
- selectedReferenceDeckId && selectedReferenceDeckId !== "none"
566
- ? selectedReferenceDeckId
567
- : null,
568
- );
590
+ const referenceDeckContext =
591
+ await loadReferenceDeckGenerationContext(referenceDeckId);
569
592
  const hydratedDesignSystemContext = await loadDesignSystemGenerationContext(
570
593
  selectedDesignSystem?.id,
571
594
  );
@@ -593,7 +616,7 @@ export default function Index() {
593
616
  referenceDeckContext,
594
617
  designSystemContext,
595
618
  "",
596
- deckLengthContext,
619
+ "Before generating, if the request or selected references leave a meaningful choice unresolved, use the `ask-question` tool to ask one concise, prompt-specific question in the inline guided-question flow. Generate the question wording and 2 to 4 options from the user's request and selected references, like Claude's design-question flow; do not use a fixed generic questionnaire. Ask only a choice that materially affects the deck, such as audience, tone, structure, or length. If the prompt already makes the choice clear, do not ask it again. Wait for the user's answer or skip before adding slides.",
597
620
  "Start a `manage-progress` run so progress appears in the app header. Add the first slide as soon as it is ready, then continue one slide at a time so the editor visibly fills in.",
598
621
  "After reading any requested or imported source material, but before adding the first slide, choose a concise, specific deck title from the user's request and source material. Call `patch-deck` with `deckId: \"" +
599
622
  deckId +
@@ -619,6 +642,108 @@ export default function Index() {
619
642
  });
620
643
  };
621
644
 
645
+ const handlePromptSubmit = useCallback(
646
+ (prompt: string, files: UploadedFile[]) => {
647
+ setNewDeckPromptOpen(false, { clearInitialPrompt: false });
648
+ setPendingDeck({ prompt, files });
649
+ setShowNewDeckReferenceStep(true);
650
+ },
651
+ [setNewDeckPromptOpen],
652
+ );
653
+
654
+ const handleReferenceSelect = useCallback(
655
+ async (selection: NewDeckReferenceSelection) => {
656
+ const pending = pendingDeck;
657
+ if (!pending) return;
658
+ if (selection.designSystemId) {
659
+ rememberReference({
660
+ id: selection.designSystemId,
661
+ kind: "design-system",
662
+ });
663
+ }
664
+ if (selection.referenceDeckId) {
665
+ rememberReference({ id: selection.referenceDeckId, kind: "deck" });
666
+ }
667
+ setShowNewDeckReferenceStep(false);
668
+ setPendingDeck(null);
669
+ await handleCreateDeckWithPrompt(
670
+ pending.prompt,
671
+ pending.files,
672
+ selection,
673
+ );
674
+ },
675
+ [handleCreateDeckWithPrompt, pendingDeck, rememberReference],
676
+ );
677
+
678
+ const handleReferenceImport = useCallback(
679
+ async (files: File[]) => {
680
+ const pending = pendingDeck;
681
+ if (!pending) return;
682
+ setReferenceImporting(true);
683
+ try {
684
+ const uploaded = await uploadPromptFiles(files);
685
+ const pptxReference = uploaded.find((file) =>
686
+ file.originalName.toLowerCase().endsWith(".pptx"),
687
+ );
688
+ let referenceSelection: NewDeckReferenceSelection = {};
689
+ let generationFiles = uploaded;
690
+ if (pptxReference) {
691
+ const imported = (await callAction("import-pptx", {
692
+ filePath: pptxReference.path,
693
+ })) as { id?: unknown };
694
+ if (typeof imported.id !== "string" || !imported.id) {
695
+ throw new Error("The imported presentation did not create a deck.");
696
+ }
697
+ await reloadDecks();
698
+ rememberReference({ id: imported.id, kind: "deck" });
699
+ referenceSelection = {
700
+ designSystemId: null,
701
+ referenceDeckId: imported.id,
702
+ };
703
+ generationFiles = uploaded.filter((file) => file !== pptxReference);
704
+ }
705
+ setShowNewDeckReferenceStep(false);
706
+ setPendingDeck(null);
707
+ await handleCreateDeckWithPrompt(
708
+ pending.prompt,
709
+ [...pending.files, ...generationFiles],
710
+ referenceSelection,
711
+ );
712
+ } catch (error) {
713
+ toast.error(t("editorToolbar.uploadFailed"), {
714
+ description:
715
+ error instanceof Error
716
+ ? error.message
717
+ : t("editorToolbar.importFailedDescription"),
718
+ });
719
+ } finally {
720
+ setReferenceImporting(false);
721
+ }
722
+ },
723
+ [
724
+ callAction,
725
+ handleCreateDeckWithPrompt,
726
+ pendingDeck,
727
+ reloadDecks,
728
+ rememberReference,
729
+ t,
730
+ ],
731
+ );
732
+
733
+ const handleReferenceSkip = useCallback(() => {
734
+ const pending = pendingDeck;
735
+ if (!pending) {
736
+ setShowNewDeckReferenceStep(false);
737
+ return;
738
+ }
739
+ setShowNewDeckReferenceStep(false);
740
+ setPendingDeck(null);
741
+ void handleCreateDeckWithPrompt(pending.prompt, pending.files, {
742
+ designSystemId: null,
743
+ referenceDeckId: null,
744
+ });
745
+ }, [handleCreateDeckWithPrompt, pendingDeck]);
746
+
622
747
  const handleConfirmDelete = () => {
623
748
  if (deckToDelete) {
624
749
  deleteDeck(deckToDelete);
@@ -933,7 +1058,7 @@ export default function Index() {
933
1058
  placeholder={t("home.newDeckPlaceholder")}
934
1059
  onSkip={handleCreateDeckBlank}
935
1060
  skipLabel={t("home.skipPrompt")}
936
- onSubmit={handleCreateDeckWithPrompt}
1061
+ onSubmit={handlePromptSubmit}
937
1062
  onBeforeUpload={(prompt, files) => {
938
1063
  if (session) return true;
939
1064
  preservePromptForSignIn(prompt, { hadFiles: files.length > 0 });
@@ -944,105 +1069,35 @@ export default function Index() {
944
1069
  draftScope={NEW_DECK_DRAFT_SCOPE}
945
1070
  initialText={newDeckInitialPrompt?.text}
946
1071
  initialTextKey={newDeckInitialPrompt?.key}
947
- >
948
- {(designSystems.length > 0 || decks.length > 0) && (
949
- <div
950
- className={cn(
951
- "grid gap-3 border-t border-border px-3.5 py-2",
952
- designSystems.length > 0 && decks.length > 0
953
- ? "grid-cols-2"
954
- : "grid-cols-1",
955
- )}
956
- >
957
- {designSystems.length > 0 && (
958
- <div className="min-w-0">
959
- <label className="mb-1.5 block text-[11px] font-medium text-muted-foreground">
960
- {t("home.designSystem")}
961
- </label>
962
- <Select
963
- value={selectedDesignSystemId || "none"}
964
- onValueChange={(value) => {
965
- designSystemAutoRef.current = false;
966
- setSelectedDesignSystemId(value);
967
- }}
968
- >
969
- <SelectTrigger className="h-8 w-full bg-accent/40 text-xs">
970
- <SelectValue placeholder={t("raw.chooseDesignSystem")} />
971
- </SelectTrigger>
972
- <SelectContent>
973
- <SelectItem value="none">{t("home.none")}</SelectItem>
974
- {designSystems.map((ds) => (
975
- <SelectItem key={ds.id} value={ds.id}>
976
- {ds.title}
977
- {ds.isDefault || ds.id === workspaceDesignSystemId
978
- ? t("home.defaultSuffix")
979
- : ""}
980
- </SelectItem>
981
- ))}
982
- </SelectContent>
983
- </Select>
984
- </div>
985
- )}
986
- {decks.length > 0 && (
987
- <div className="min-w-0">
988
- <label className="mb-1.5 block text-[11px] font-medium text-muted-foreground">
989
- {t("home.referenceDeck")}
990
- </label>
991
- <Select
992
- value={selectedReferenceDeckId || "none"}
993
- onValueChange={(value) => {
994
- referenceDeckAutoRef.current = false;
995
- setSelectedReferenceDeckId(value);
996
- }}
997
- >
998
- <SelectTrigger className="h-8 w-full bg-accent/40 text-xs">
999
- <SelectValue
1000
- placeholder={t("home.referenceDeckPlaceholder")}
1001
- />
1002
- </SelectTrigger>
1003
- <SelectContent>
1004
- <SelectItem value="none">
1005
- {t("home.referenceDeckNone")}
1006
- </SelectItem>
1007
- {starredDecks.length > 0 && (
1008
- <SelectGroup>
1009
- <SelectLabel>
1010
- {t("home.referenceDeckStarredGroup")}
1011
- </SelectLabel>
1012
- {starredDecks.map((deck) => (
1013
- <SelectItem key={deck.id} value={deck.id}>
1014
- {deck.title}
1015
- {deck.id === workspaceReferenceDeckId
1016
- ? t("home.defaultSuffix")
1017
- : ""}
1018
- </SelectItem>
1019
- ))}
1020
- </SelectGroup>
1021
- )}
1022
- {unstarredDecks.length > 0 && (
1023
- <SelectGroup>
1024
- {starredDecks.length > 0 && (
1025
- <SelectLabel>
1026
- {t("home.referenceDeckOtherGroup")}
1027
- </SelectLabel>
1028
- )}
1029
- {unstarredDecks.map((deck) => (
1030
- <SelectItem key={deck.id} value={deck.id}>
1031
- {deck.title}
1032
- {deck.id === workspaceReferenceDeckId
1033
- ? t("home.defaultSuffix")
1034
- : ""}
1035
- </SelectItem>
1036
- ))}
1037
- </SelectGroup>
1038
- )}
1039
- </SelectContent>
1040
- </Select>
1041
- </div>
1042
- )}
1043
- </div>
1044
- )}
1045
- </PromptPopover>
1072
+ />
1073
+
1074
+ <NewDeckReferenceStep
1075
+ open={showNewDeckReferenceStep}
1076
+ onOpenChange={(open) => {
1077
+ if (!open) handleReferenceSkip();
1078
+ }}
1079
+ designSystems={designSystems}
1080
+ decks={decks}
1081
+ defaultDesignSystemId={initialDesignSystemId}
1082
+ defaultReferenceDeckId={workspaceReferenceDeckId}
1083
+ recentReferences={recentReferences}
1084
+ onSelect={(selection) => void handleReferenceSelect(selection)}
1085
+ onImport={handleReferenceImport}
1086
+ onSkip={handleReferenceSkip}
1087
+ importing={referenceImporting}
1088
+ title={t("home.newDeckPromptTitle")}
1089
+ description={t("home.referenceDeckPlaceholder")}
1090
+ designSystemLabel={t("home.designSystem")}
1091
+ referenceDeckLabel={t("home.referenceDeck")}
1092
+ chooseDeckLabel={t("home.referenceDeckPlaceholder")}
1093
+ importFileLabel={t("editorToolbar.importFile")}
1094
+ importingLabel={t("editorToolbar.importing")}
1095
+ skipLabel={t("home.referenceDeckNone")}
1096
+ defaultSuffix={t("home.defaultSuffix")}
1097
+ starredLabel={t("home.referenceDeckStarredGroup")}
1098
+ otherDecksLabel={t("home.referenceDeckOtherGroup")}
1099
+ promptNote={pendingDeck?.prompt}
1100
+ />
1046
1101
 
1047
1102
  {/* Sign-in required to create a deck. Shown when an unauthenticated
1048
1103
  user submits a prompt — the typed prompt is preserved in