@agent-native/core 0.153.10 → 0.154.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 (149) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/AGENTS.md +20 -30
  3. package/corpus/templates/analytics/actions/search-dashboard-references.ts +58 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  5. package/corpus/templates/analytics/app/lib/utils.ts +1 -1
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
  7. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
  8. package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
  9. package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
  10. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
  11. package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
  12. package/corpus/templates/calendar/app/lib/utils.ts +1 -1
  13. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
  14. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
  15. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  16. package/corpus/templates/clips/app/lib/utils.ts +5 -6
  17. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
  18. package/corpus/templates/clips/desktop/src/app.tsx +26 -12
  19. package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
  20. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
  21. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
  22. package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
  23. package/corpus/templates/content/actions/resolve-content-landing.ts +162 -32
  24. package/corpus/templates/content/app/i18n-data.ts +10 -10
  25. package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
  26. package/corpus/templates/content/shared/content-landing.ts +5 -0
  27. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
  28. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
  29. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
  30. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
  31. package/corpus/templates/design/actions/get-design-system.ts +103 -3
  32. package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
  33. package/corpus/templates/design/actions/import-github.ts +32 -138
  34. package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
  35. package/corpus/templates/design/actions/present-design-variants.ts +93 -4
  36. package/corpus/templates/design/actions/run-design-audit.ts +229 -0
  37. package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
  38. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
  39. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
  40. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
  41. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
  42. package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
  43. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  44. package/corpus/templates/design/app/i18n-data.ts +32 -1
  45. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
  46. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
  47. package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
  48. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
  49. package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
  50. package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
  51. package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
  52. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
  53. package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
  54. package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
  55. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
  56. package/corpus/templates/design/shared/design-review.ts +1 -0
  57. package/corpus/templates/design/shared/html-integrity.ts +171 -0
  58. package/corpus/templates/mail/app/lib/utils.ts +1 -1
  59. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
  60. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
  61. package/corpus/templates/slides/actions/patch-deck.ts +12 -3
  62. package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
  63. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  64. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
  65. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +73 -1
  66. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +64 -34
  67. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +34 -41
  68. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
  69. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
  70. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +15 -0
  71. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
  72. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
  73. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
  74. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
  75. package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
  76. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
  77. package/corpus/templates/slides/app/context/DeckContext.tsx +19 -31
  78. package/corpus/templates/slides/app/global.css +24 -0
  79. package/corpus/templates/slides/app/hooks/use-navigation-state.ts +20 -17
  80. package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
  81. package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
  82. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
  83. package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
  84. package/corpus/templates/slides/app/lib/image-download.ts +84 -0
  85. package/corpus/templates/slides/app/lib/utils.ts +1 -1
  86. package/corpus/templates/slides/app/pages/DeckEditor.tsx +9 -0
  87. package/corpus/templates/slides/app/pages/Index.tsx +25 -9
  88. package/corpus/templates/slides/changelog/2026-08-13-clicking-a-slide-thumbnail-now-focuses-it-so-the-slide-shortc.md +6 -0
  89. package/corpus/templates/slides/changelog/2026-08-13-duplicating-or-restoring-a-slide-now-keeps-its-transition-ani.md +6 -0
  90. package/corpus/templates/slides/changelog/2026-08-13-opening-presentation-mode-from-the-agent-now-starts-on-the-re.md +6 -0
  91. package/corpus/templates/slides/changelog/2026-08-13-slide-transitions-can-be-set-from-a-visible-control-in-the-sl.md +6 -0
  92. package/corpus/templates/slides/netlify.toml +1 -0
  93. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
  94. package/dist/a2a/audience.d.ts +2 -0
  95. package/dist/a2a/audience.js +23 -0
  96. package/dist/a2a/client.d.ts +4 -0
  97. package/dist/a2a/client.js +32 -14
  98. package/dist/a2a/server.d.ts +9 -4
  99. package/dist/a2a/server.js +52 -25
  100. package/dist/a2a/types.d.ts +4 -4
  101. package/dist/agent/production-agent.js +1 -1
  102. package/dist/brand-kit/types.d.ts +10 -2
  103. package/dist/cli/code.js +1 -1
  104. package/dist/cli/index.js +30 -1
  105. package/dist/cli/native-dependencies.d.ts +24 -0
  106. package/dist/cli/native-dependencies.js +264 -0
  107. package/dist/cli/plan-local.js +1 -1
  108. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  109. package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
  110. package/dist/cli/visualize-repo.js +1 -1
  111. package/dist/client/FeedbackButton.js +12 -12
  112. package/dist/client/agent-chat-adapter.js +3 -4
  113. package/dist/client/assistant-ui-recovery.d.ts +1 -1
  114. package/dist/client/assistant-ui-recovery.js +4 -0
  115. package/dist/client/chat/message-components.d.ts +11 -3
  116. package/dist/client/chat/message-components.js +31 -5
  117. package/dist/client/chat/tool-call-display.d.ts +2 -1
  118. package/dist/client/chat/tool-call-display.js +33 -15
  119. package/dist/client/db-admin/SqlEditor.js +1 -1
  120. package/dist/client/dev-overlay/DevOverlay.js +2 -2
  121. package/dist/client/extensions/portable-extension.js +4 -0
  122. package/dist/client/guided-questions.d.ts +8 -0
  123. package/dist/client/guided-questions.js +12 -3
  124. package/dist/client/sharing/ShareButton.js +16 -24
  125. package/dist/client/sse-event-processor.js +35 -15
  126. package/dist/client/tool-display.d.ts +9 -0
  127. package/dist/client/tool-display.js +39 -0
  128. package/dist/client/vite-dev-recovery-script.js +1 -1
  129. package/dist/collab/awareness.d.ts +2 -2
  130. package/dist/collab/routes.d.ts +1 -1
  131. package/dist/collab/struct-routes.d.ts +1 -1
  132. package/dist/extensions/html-shell.js +5 -0
  133. package/dist/notifications/routes.d.ts +1 -1
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
  136. package/dist/server/agent-capabilities.js +0 -0
  137. package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -13
  138. package/dist/server/agent-chat/action-filters-a2a.js +59 -23
  139. package/dist/server/agent-chat/prompt-resources.js +17 -16
  140. package/dist/server/agent-chat-plugin.d.ts +9 -0
  141. package/dist/server/agent-chat-plugin.js +29 -7
  142. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  143. package/dist/server/builder-design-systems.d.ts +33 -1
  144. package/dist/server/builder-design-systems.js +293 -11
  145. package/dist/server/design-token-utils.d.ts +11 -0
  146. package/dist/server/design-token-utils.js +68 -16
  147. package/dist/server/index.d.ts +1 -1
  148. package/dist/server/index.js +1 -1
  149. package/package.json +2 -2
@@ -31,7 +31,6 @@ import {
31
31
  IconCode,
32
32
  IconCopy,
33
33
  IconFileTypePdf,
34
- IconPlugConnected,
35
34
  } from "@tabler/icons-react";
36
35
  import { useTheme } from "next-themes";
37
36
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -122,6 +121,9 @@ interface EditorToolbarProps {
122
121
  textBoxMode?: boolean;
123
122
  /** Toggle the add-text-box tool */
124
123
  onToggleTextBoxMode?: () => void;
124
+ onChangeSlideTransition?: (
125
+ transition: NonNullable<Slide["transition"]>,
126
+ ) => void;
125
127
  /** Duplicate the current deck */
126
128
  onDuplicateDeck?: () => void;
127
129
  /** Export the deck as PDF */
@@ -177,6 +179,7 @@ export default function EditorToolbar({
177
179
  onTogglePinMode,
178
180
  textBoxMode,
179
181
  onToggleTextBoxMode,
182
+ onChangeSlideTransition,
180
183
  onDuplicateDeck,
181
184
  onExportPdf,
182
185
  onExportPptx,
@@ -439,24 +442,14 @@ export default function EditorToolbar({
439
442
  },
440
443
  );
441
444
  if (onExportGoogleSlides) {
442
- commands.push(
443
- {
444
- id: "connect-google",
445
- group: "deck",
446
- label: t("editorExport.connectGoogle"),
447
- keywords: ["google", "drive", "connect"],
448
- icon: IconPlugConnected,
449
- run: () => void exportMenuRef.current?.connectGoogle(),
450
- },
451
- {
452
- id: "open-in-google-slides",
453
- group: "deck",
454
- label: t("editorExport.openInGoogleSlides"),
455
- keywords: ["google", "slides", "export"],
456
- icon: IconBrandGoogle,
457
- run: () => void exportMenuRef.current?.exportGoogleSlides(),
458
- },
459
- );
445
+ commands.push({
446
+ id: "export-to-google-slides",
447
+ group: "deck",
448
+ label: t("editorExport.openInGoogleSlides"),
449
+ keywords: ["google", "slides", "export"],
450
+ icon: IconBrandGoogle,
451
+ run: () => void exportMenuRef.current?.exportGoogleSlides(),
452
+ });
460
453
  }
461
454
  if (onDuplicateDeck) {
462
455
  commands.push({
@@ -578,6 +571,8 @@ export default function EditorToolbar({
578
571
  onDuplicateCurrentSlide={onDuplicateCurrentSlide}
579
572
  textBoxMode={textBoxMode}
580
573
  onToggleTextBoxMode={onToggleTextBoxMode}
574
+ slideTransition={currentSlide?.transition}
575
+ onChangeSlideTransition={onChangeSlideTransition}
581
576
  />
582
577
  )}
583
578
 
@@ -632,7 +627,7 @@ export default function EditorToolbar({
632
627
  agentActive={agentActive}
633
628
  showAgentEditingDot={false}
634
629
  currentUserEmail={currentUserEmail}
635
- className="flex-shrink-0 mr-0.5"
630
+ className="ml-auto flex-shrink-0 mr-0.5 pl-2"
636
631
  />
637
632
 
638
633
  {/* Consolidated editor menu */}
@@ -657,26 +652,8 @@ export default function EditorToolbar({
657
652
  </Tooltip>
658
653
  <DropdownMenuContent
659
654
  align="end"
660
- className="max-h-[min(80vh,32rem)] w-64 overflow-y-auto"
655
+ className="max-h-[90vh] w-64 overflow-y-auto"
661
656
  >
662
- {canEdit && (
663
- <>
664
- <DropdownMenuLabel>
665
- {t("editorToolbar.media")}
666
- </DropdownMenuLabel>
667
- <DropdownMenuGroup>
668
- <DropdownMenuItem onSelect={onGenerateImage}>
669
- <IconPhoto className="size-4" />
670
- {t("editorToolbar.generateImage")}
671
- </DropdownMenuItem>
672
- <DropdownMenuItem onSelect={onOpenAssetLibrary}>
673
- <IconFolderOpen className="size-4" />
674
- {t("editorToolbar.assetLibrary")}
675
- </DropdownMenuItem>
676
- </DropdownMenuGroup>
677
- </>
678
- )}
679
-
680
657
  {((canEdit &&
681
658
  (onToggleAnimations || onToggleTweaks || onToggleDrawMode)) ||
682
659
  (canComment && onTogglePinMode)) && (
@@ -742,12 +719,28 @@ export default function EditorToolbar({
742
719
  </>
743
720
  )}
744
721
 
745
- {onToggleComments && (
722
+ {canEdit && (
746
723
  <>
747
724
  <DropdownMenuSeparator />
748
725
  <DropdownMenuLabel>
749
- {t("editorToolbar.comments")}
726
+ {t("editorToolbar.media")}
750
727
  </DropdownMenuLabel>
728
+ <DropdownMenuGroup>
729
+ <DropdownMenuItem onSelect={onGenerateImage}>
730
+ <IconPhoto className="size-4" />
731
+ {t("editorToolbar.generateImage")}
732
+ </DropdownMenuItem>
733
+ <DropdownMenuItem onSelect={onOpenAssetLibrary}>
734
+ <IconFolderOpen className="size-4" />
735
+ {t("editorToolbar.assetLibrary")}
736
+ </DropdownMenuItem>
737
+ </DropdownMenuGroup>
738
+ </>
739
+ )}
740
+
741
+ {onToggleComments && (
742
+ <>
743
+ <DropdownMenuSeparator />
751
744
  <DropdownMenuItem
752
745
  onSelect={onToggleComments}
753
746
  className={
@@ -11,7 +11,6 @@ import {
11
11
  IconCopy,
12
12
  IconShare2,
13
13
  IconBrandGoogle,
14
- IconPlugConnected,
15
14
  } from "@tabler/icons-react";
16
15
  import { forwardRef, useImperativeHandle, useRef, useState } from "react";
17
16
  import { toast } from "sonner";
@@ -32,6 +31,9 @@ import {
32
31
  DropdownMenuLabel,
33
32
  DropdownMenuSeparator,
34
33
  DropdownMenuTrigger,
34
+ DropdownMenuSub,
35
+ DropdownMenuSubContent,
36
+ DropdownMenuSubTrigger,
35
37
  } from "@/components/ui/dropdown-menu";
36
38
  import type { GoogleSlidesExportResult } from "@/lib/export-google-slides-client";
37
39
 
@@ -53,7 +55,6 @@ interface ExportMenuProps {
53
55
  }
54
56
 
55
57
  export interface ExportMenuHandle {
56
- connectGoogle: () => Promise<void>;
57
58
  exportGoogleSlides: () => Promise<void>;
58
59
  exportHtml: () => Promise<void>;
59
60
  exportPptx: () => Promise<void>;
@@ -121,44 +122,7 @@ export const ExportMenu = forwardRef<ExportMenuHandle, ExportMenuProps>(
121
122
  }
122
123
  };
123
124
 
124
- const handleExportGoogleSlides = async () => {
125
- if (!onExportGoogleSlides) return;
126
- // Opened up-front: browsers only honour window.open() inside the click
127
- // gesture, and building the PPTX is async.
128
- const target = window.open("", "_blank");
129
- googleSlidesImportTarget.current = target;
130
- try {
131
- const result = await onExportGoogleSlides();
132
- if (result.url !== null) {
133
- googleSlidesImportTarget.current = null;
134
- if (target) target.location.href = result.url;
135
- toast.success(t("editorExport.googleSlidesCreated"), {
136
- description: t("editorExport.googleSlidesCreatedHint"),
137
- });
138
- return;
139
- }
140
- if (target) target.location.href = GOOGLE_SLIDES_IMPORT_URL;
141
- setGoogleSlidesImportOpen(true);
142
- // The deck did not reach Drive. Saying "success" here is why users read
143
- // the .pptx download as the intended result and never learn that their
144
- // Google account is unconnected or that Drive rejected the upload.
145
- toast.warning(t("editorExport.googleSlidesDownloaded"), {
146
- description: `${result.reason} ${t("editorExport.googleSlidesImportHint")}`,
147
- });
148
- } catch (err) {
149
- googleSlidesImportTarget.current = null;
150
- target?.close();
151
- console.error("Export failed:", err);
152
- toast.error(t("editorExport.exportFailed"), {
153
- description:
154
- err instanceof Error
155
- ? err.message
156
- : t("editorExport.exportGoogleSlidesError"),
157
- });
158
- }
159
- };
160
-
161
- const handleConnectGoogle = async () => {
125
+ const handleConnectGoogle = async (target?: Window | null) => {
162
126
  const authUrl = new URL(
163
127
  agentNativePath("/_agent-native/google-docs/auth-url"),
164
128
  window.location.origin,
@@ -168,11 +132,9 @@ export const ExportMenu = forwardRef<ExportMenuHandle, ExportMenuProps>(
168
132
  window.location.pathname + window.location.search,
169
133
  );
170
134
 
171
- const popup = window.open(
172
- "",
173
- "google-docs-oauth",
174
- "popup,width=520,height=720",
175
- );
135
+ const popup =
136
+ target ??
137
+ window.open("", "google-docs-oauth", "popup,width=520,height=720");
176
138
  if (!popup) {
177
139
  toast.error(t("editorExport.exportFailed"), {
178
140
  description: t("editorExport.exportGoogleSlidesError"),
@@ -209,6 +171,49 @@ export const ExportMenu = forwardRef<ExportMenuHandle, ExportMenuProps>(
209
171
  }
210
172
  };
211
173
 
174
+ const handleExportGoogleSlides = async () => {
175
+ if (!onExportGoogleSlides) return;
176
+ // Opened up-front: browsers only honour window.open() inside the click
177
+ // gesture, and building the PPTX is async. If the account is missing,
178
+ // the same tab becomes the OAuth popup so the export action owns setup.
179
+ const target = window.open("", "_blank");
180
+ googleSlidesImportTarget.current = target;
181
+ try {
182
+ const result = await onExportGoogleSlides();
183
+ if ("requiresConnection" in result && result.requiresConnection) {
184
+ googleSlidesImportTarget.current = null;
185
+ await handleConnectGoogle(target);
186
+ return;
187
+ }
188
+ if (result.url !== null) {
189
+ googleSlidesImportTarget.current = null;
190
+ if (target) target.location.href = result.url;
191
+ toast.success(t("editorExport.googleSlidesCreated"), {
192
+ description: t("editorExport.googleSlidesCreatedHint"),
193
+ });
194
+ return;
195
+ }
196
+ if (target) target.location.href = GOOGLE_SLIDES_IMPORT_URL;
197
+ setGoogleSlidesImportOpen(true);
198
+ // The deck did not reach Drive. Saying "success" here is why users read
199
+ // the .pptx download as the intended result and never learn that Drive
200
+ // rejected the upload.
201
+ toast.warning(t("editorExport.googleSlidesDownloaded"), {
202
+ description: `${result.reason} ${t("editorExport.googleSlidesImportHint")}`,
203
+ });
204
+ } catch (err) {
205
+ googleSlidesImportTarget.current = null;
206
+ target?.close();
207
+ console.error("Export failed:", err);
208
+ toast.error(t("editorExport.exportFailed"), {
209
+ description:
210
+ err instanceof Error
211
+ ? err.message
212
+ : t("editorExport.exportGoogleSlidesError"),
213
+ });
214
+ }
215
+ };
216
+
212
217
  const handleExportHtml = async () => {
213
218
  try {
214
219
  const res = await fetch(`${appBasePath()}/api/exports/html`, {
@@ -241,79 +246,102 @@ export const ExportMenu = forwardRef<ExportMenuHandle, ExportMenuProps>(
241
246
  useImperativeHandle(
242
247
  ref,
243
248
  () => ({
244
- connectGoogle: handleConnectGoogle,
245
249
  exportGoogleSlides: handleExportGoogleSlides,
246
250
  exportHtml: handleExportHtml,
247
251
  exportPptx: handleExportPptx,
248
252
  }),
249
- [
250
- handleConnectGoogle,
251
- handleExportGoogleSlides,
252
- handleExportHtml,
253
- handleExportPptx,
254
- ],
253
+ [handleExportGoogleSlides, handleExportHtml, handleExportPptx],
255
254
  );
256
255
 
257
- const menuContent = (
256
+ const exportActions = (
257
+ <>
258
+ <DropdownMenuItem onClick={handleExportHtml} className="cursor-pointer">
259
+ <IconCode className="size-4" />
260
+ {t("editorExport.downloadHtml")}
261
+ </DropdownMenuItem>
262
+ <DropdownMenuItem onClick={onExportPdf} className="cursor-pointer">
263
+ <IconFileTypePdf className="size-4" />
264
+ {t("editorExport.exportPdf")}
265
+ </DropdownMenuItem>
266
+ <DropdownMenuItem onClick={handleExportPptx} className="cursor-pointer">
267
+ <IconDownload className="size-4" />
268
+ {t("editorExport.exportPptx")}
269
+ </DropdownMenuItem>
270
+ {onExportGoogleSlides && (
271
+ <DropdownMenuItem
272
+ onClick={handleExportGoogleSlides}
273
+ className="cursor-pointer"
274
+ >
275
+ <IconBrandGoogle className="size-4" />
276
+ {t("editorExport.openInGoogleSlides")}
277
+ </DropdownMenuItem>
278
+ )}
279
+ </>
280
+ );
281
+
282
+ const shareActions = (
258
283
  <>
259
- <DropdownMenuLabel className="text-[11px] text-muted-foreground">
260
- {t("editorExport.exportAndDuplicate")}
261
- </DropdownMenuLabel>
262
284
  {onShareTeam && (
263
285
  <DropdownMenuItem onClick={onShareTeam} className="cursor-pointer">
264
- <IconShare2 className="w-4 h-4 mr-2" />
286
+ <IconShare2 className="size-4" />
265
287
  {t("editorExport.shareWithTeam")}
266
288
  </DropdownMenuItem>
267
289
  )}
268
290
  {onShareLink && (
269
291
  <DropdownMenuItem onClick={onShareLink} className="cursor-pointer">
270
- <IconShare2 className="w-4 h-4 mr-2" />
292
+ <IconShare2 className="size-4" />
271
293
  {t("editorExport.publicShareLink")}
272
294
  </DropdownMenuItem>
273
295
  )}
296
+ </>
297
+ );
298
+
299
+ const duplicateAction = (
300
+ <DropdownMenuItem onClick={onDuplicate} className="cursor-pointer">
301
+ <IconCopy className="size-4" />
302
+ {t("editorExport.duplicateDeck")}
303
+ </DropdownMenuItem>
304
+ );
305
+
306
+ const menuContent = (
307
+ <>
308
+ <DropdownMenuLabel className="text-[11px] text-muted-foreground">
309
+ {t("editorExport.exportAndDuplicate")}
310
+ </DropdownMenuLabel>
311
+ {shareActions}
274
312
  <DropdownMenuSeparator />
275
- <DropdownMenuItem onClick={handleExportHtml} className="cursor-pointer">
276
- <IconCode className="w-4 h-4 mr-2" />
277
- {t("editorExport.downloadHtml")}
278
- </DropdownMenuItem>
279
- <DropdownMenuItem onClick={onExportPdf} className="cursor-pointer">
280
- <IconFileTypePdf className="w-4 h-4 mr-2" />
281
- {t("editorExport.exportPdf")}
282
- </DropdownMenuItem>
283
- <DropdownMenuItem onClick={handleExportPptx} className="cursor-pointer">
284
- <IconDownload className="w-4 h-4 mr-2" />
285
- {t("editorExport.exportPptx")}
286
- </DropdownMenuItem>
287
- {onExportGoogleSlides && (
313
+ {exportActions}
314
+ <DropdownMenuSeparator />
315
+ {duplicateAction}
316
+ </>
317
+ );
318
+
319
+ const inlineMenuContent = (
320
+ <>
321
+ {onShareTeam || onShareLink ? (
288
322
  <>
289
- <DropdownMenuItem
290
- onClick={handleConnectGoogle}
291
- className="cursor-pointer"
292
- >
293
- <IconPlugConnected className="w-4 h-4 mr-2" />
294
- {t("editorExport.connectGoogle")}
295
- </DropdownMenuItem>
296
- <DropdownMenuItem
297
- onClick={handleExportGoogleSlides}
298
- className="cursor-pointer"
299
- >
300
- <IconBrandGoogle className="w-4 h-4 mr-2" />
301
- {t("editorExport.openInGoogleSlides")}
302
- </DropdownMenuItem>
323
+ {shareActions}
324
+ <DropdownMenuSeparator />
303
325
  </>
304
- )}
326
+ ) : null}
327
+ <DropdownMenuSub>
328
+ <DropdownMenuSubTrigger className="cursor-pointer gap-2">
329
+ <IconUpload className="size-4" />
330
+ {t("editorExport.export")}
331
+ </DropdownMenuSubTrigger>
332
+ <DropdownMenuSubContent className="w-56">
333
+ {exportActions}
334
+ </DropdownMenuSubContent>
335
+ </DropdownMenuSub>
305
336
  <DropdownMenuSeparator />
306
- <DropdownMenuItem onClick={onDuplicate} className="cursor-pointer">
307
- <IconCopy className="w-4 h-4 mr-2" />
308
- {t("editorExport.duplicateDeck")}
309
- </DropdownMenuItem>
337
+ {duplicateAction}
310
338
  </>
311
339
  );
312
340
 
313
341
  return (
314
342
  <>
315
343
  {inline ? (
316
- menuContent
344
+ inlineMenuContent
317
345
  ) : (
318
346
  <DropdownMenu>
319
347
  <DropdownMenuTrigger asChild>
@@ -3,6 +3,7 @@ import {
3
3
  IconPhoto,
4
4
  IconFolderOpen,
5
5
  IconUpload,
6
+ IconDownload,
6
7
  IconMaximize,
7
8
  IconMinimize,
8
9
  } from "@tabler/icons-react";
@@ -11,20 +12,24 @@ import { createPortal } from "react-dom";
11
12
 
12
13
  interface ImageOverlayProps {
13
14
  anchorRect: DOMRect;
15
+ src: string;
14
16
  objectFit: "cover" | "contain";
15
17
  onGenerate: () => void;
16
18
  onLibrary: () => void;
17
19
  onUpload: () => void;
20
+ onDownload: () => void;
18
21
  onToggleObjectFit: () => void;
19
22
  onClose: () => void;
20
23
  }
21
24
 
22
25
  export default function ImageOverlay({
23
26
  anchorRect,
27
+ src,
24
28
  objectFit,
25
29
  onGenerate,
26
30
  onLibrary,
27
31
  onUpload,
32
+ onDownload,
28
33
  onToggleObjectFit,
29
34
  onClose,
30
35
  }: ImageOverlayProps) {
@@ -100,6 +105,17 @@ export default function ImageOverlay({
100
105
  <IconUpload className="w-3.5 h-3.5 text-muted-foreground" />
101
106
  Upload
102
107
  </button>
108
+ <button
109
+ onClick={() => {
110
+ onDownload();
111
+ onClose();
112
+ }}
113
+ className="image-overlay-btn"
114
+ disabled={!src}
115
+ >
116
+ <IconDownload className="w-3.5 h-3.5 text-muted-foreground" />
117
+ Download
118
+ </button>
103
119
  <div className="mx-1.5 border-t border-border" />
104
120
  <button onClick={onToggleObjectFit} className="image-overlay-btn">
105
121
  {objectFit === "cover" ? (
@@ -176,6 +176,21 @@ export function NewDeckReferenceStep({
176
176
  const chooseSource = (
177
177
  kind: NonNullable<NewDeckReferenceSelection["referenceSource"]>["kind"],
178
178
  ) => {
179
+ const isAlreadySelected =
180
+ selectedSource?.kind === kind ||
181
+ (kind === "google-docs" && importedReference?.source === "google-slides");
182
+ if (isAlreadySelected) {
183
+ setSelectedSource(null);
184
+ if (importedReference) {
185
+ // The import also set the reference deck. Leaving that id behind would
186
+ // submit a deck the UI no longer shows as selected.
187
+ setSelectedReferenceDeckId((current) =>
188
+ current === importedReference.id ? null : current,
189
+ );
190
+ setImportedReference(null);
191
+ }
192
+ return;
193
+ }
179
194
  setSelectedSource({ kind, value: "" });
180
195
  if (kind === "website" || kind === "figma") {
181
196
  setSelectedDesignSystemId(null);