@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
@@ -19,6 +19,7 @@ import {
19
19
  IconCheck,
20
20
  IconExternalLink,
21
21
  IconChevronDown,
22
+ IconRefresh,
22
23
  } from "@tabler/icons-react";
23
24
  import { useState, useCallback, useEffect, useRef, useMemo } from "react";
24
25
  import { toast } from "sonner";
@@ -59,6 +60,8 @@ interface DesignSystemSetupProps {
59
60
  interface GitHubLink {
60
61
  id: string;
61
62
  url: string;
63
+ ref?: string;
64
+ include?: string[];
62
65
  }
63
66
 
64
67
  interface UploadedFile {
@@ -81,6 +84,13 @@ interface BuilderSourceDetails {
81
84
  tokenValues?: Record<string, string>;
82
85
  docCount?: number;
83
86
  warning?: string;
87
+ githubSources?: Array<{
88
+ repoUrl: string;
89
+ ref?: string;
90
+ include?: string[];
91
+ exclude?: string[];
92
+ }>;
93
+ syncedAt?: string;
84
94
  }
85
95
 
86
96
  interface ExistingDesignSystem {
@@ -91,6 +101,25 @@ interface ExistingDesignSystem {
91
101
  builder?: BuilderSourceDetails | null;
92
102
  }
93
103
 
104
+ interface BuilderIndexInput {
105
+ projectName?: string;
106
+ description?: string;
107
+ githubSources?: Array<{
108
+ repoUrl: string;
109
+ ref?: string;
110
+ include?: string[];
111
+ exclude?: string[];
112
+ }>;
113
+ githubRepoUrl?: string;
114
+ codeFiles?: Array<{
115
+ filename: string;
116
+ content: string;
117
+ mimeType?: string;
118
+ encoding?: "utf8" | "base64";
119
+ }>;
120
+ designMd?: string;
121
+ }
122
+
94
123
  type OtherSource = "brand" | "code" | "files" | "existing" | "context";
95
124
 
96
125
  function normalizeWebsiteUrlInput(input: string): string | null {
@@ -134,6 +163,8 @@ export function DesignSystemSetup({
134
163
  const [websiteUrl, setWebsiteUrl] = useState("");
135
164
  const [websiteUrls, setWebsiteUrls] = useState<string[]>([]);
136
165
  const [githubUrl, setGithubUrl] = useState("");
166
+ const [githubRef, setGithubRef] = useState("");
167
+ const [githubPaths, setGithubPaths] = useState("");
137
168
  const [githubLinks, setGithubLinks] = useState<GitHubLink[]>([]);
138
169
  const [codeFiles, setCodeFiles] = useState<UploadedFile[]>([]);
139
170
  const [docFiles, setDocFiles] = useState<UploadedFile[]>([]);
@@ -211,6 +242,13 @@ export function DesignSystemSetup({
211
242
  const imageInputRef = useRef<HTMLInputElement>(null);
212
243
  const figInputRef = useRef<HTMLInputElement>(null);
213
244
  const updateSystemMutation = useActionMutation("update-design-system");
245
+ const syncSystemMutation = useActionMutation(
246
+ "sync-design-system-with-builder",
247
+ );
248
+ const indexSystemMutation = useActionMutation<
249
+ BuilderIndexResult,
250
+ BuilderIndexInput
251
+ >("index-design-system-with-builder");
214
252
 
215
253
  const {
216
254
  data: existingDs,
@@ -267,6 +305,8 @@ export function DesignSystemSetup({
267
305
  setWebsiteUrl("");
268
306
  setWebsiteUrls([]);
269
307
  setGithubUrl("");
308
+ setGithubRef("");
309
+ setGithubPaths("");
270
310
  setGithubLinks([]);
271
311
  setCodeFiles([]);
272
312
  setDocFiles([]);
@@ -323,9 +363,23 @@ export function DesignSystemSetup({
323
363
  const addGithubLink = useCallback(() => {
324
364
  const url = githubUrl.trim();
325
365
  if (!url) return;
326
- setGithubLinks((prev) => [...prev, { id: crypto.randomUUID(), url }]);
366
+ const include = githubPaths
367
+ .split(/[\n,]/)
368
+ .map((path) => path.trim().replace(/^\/+|\/+$/g, ""))
369
+ .filter(Boolean);
370
+ setGithubLinks((prev) => [
371
+ ...prev,
372
+ {
373
+ id: crypto.randomUUID(),
374
+ url,
375
+ ...(githubRef.trim() ? { ref: githubRef.trim() } : {}),
376
+ ...(include.length > 0 ? { include: [...new Set(include)] } : {}),
377
+ },
378
+ ]);
327
379
  setGithubUrl("");
328
- }, [githubUrl]);
380
+ setGithubRef("");
381
+ setGithubPaths("");
382
+ }, [githubPaths, githubRef, githubUrl]);
329
383
 
330
384
  const readTextFiles = useCallback(
331
385
  (
@@ -432,12 +486,66 @@ export function DesignSystemSetup({
432
486
  }
433
487
  };
434
488
 
489
+ const handleSync = useCallback(async () => {
490
+ if (!editingId || !existingDs?.builder?.githubSources?.length) return;
491
+ setGenerating(true);
492
+ try {
493
+ await syncSystemMutation.mutateAsync({ id: editingId });
494
+ toast.success(t("designSystemSetup.syncStarted"));
495
+ } catch (error) {
496
+ toast.error(t("designSystemSetup.syncFailed"), {
497
+ description:
498
+ error instanceof Error
499
+ ? error.message
500
+ : t("designSystemSetup.syncFailed"),
501
+ });
502
+ } finally {
503
+ setGenerating(false);
504
+ }
505
+ }, [editingId, existingDs, syncSystemMutation, t]);
506
+
435
507
  const handleGenerate = useCallback(async () => {
436
508
  if (editingId) {
437
509
  await handleEditSave();
438
510
  return;
439
511
  }
440
512
 
513
+ const isGithubOnlySource =
514
+ githubLinks.length > 0 &&
515
+ websiteUrls.length === 0 &&
516
+ codeFiles.length === 0 &&
517
+ !builderIndexResult &&
518
+ docFiles.length === 0 &&
519
+ imageFiles.length === 0 &&
520
+ !selectedSystemId &&
521
+ !brandNotes.trim() &&
522
+ !customInstructions.trim();
523
+ if (isGithubOnlySource) {
524
+ setGenerating(true);
525
+ try {
526
+ await indexSystemMutation.mutateAsync({
527
+ projectName: companyName.trim() || undefined,
528
+ githubSources: githubLinks.map((link) => ({
529
+ repoUrl: link.url,
530
+ ...(link.ref ? { ref: link.ref } : {}),
531
+ ...(link.include?.length ? { include: link.include } : {}),
532
+ })),
533
+ });
534
+ toast.success(t("designSystemSetup.generationStarted"));
535
+ onComplete();
536
+ } catch (error) {
537
+ toast.error(t("designSystemSetup.updateFailed"), {
538
+ description:
539
+ error instanceof Error
540
+ ? error.message
541
+ : t("designSystemSetup.updateFailed"),
542
+ });
543
+ } finally {
544
+ setGenerating(false);
545
+ }
546
+ return;
547
+ }
548
+
441
549
  const requestedTitle = companyName.trim();
442
550
  const localDesignSystemId = builderIndexResult?.localDesignSystemId;
443
551
  if (requestedTitle && localDesignSystemId) {
@@ -485,7 +593,15 @@ export function DesignSystemSetup({
485
593
 
486
594
  if (githubLinks.length > 0) {
487
595
  parts.push(
488
- `\n## Connect Code: GitHub Repositories\nStart Builder DSI indexing for each repository with \`index-design-system-with-builder\`:\n${githubLinks.map((l) => `- ${l.url}`).join("\n")}\n\nBuilder is the source of truth for repo/code design-system indexing. The action also creates a local selectable proxy design system for Slides flows. If Builder is not connected, stop and tell me to connect Builder (free tier available) from Settings.`,
596
+ `\n## Connect Code: GitHub Repositories\nMake one call to \`index-design-system-with-builder\` with \`githubSources\` set to this JSON array:\n\n\`\`\`json\n${JSON.stringify(
597
+ githubLinks.map((link) => ({
598
+ repoUrl: link.url,
599
+ ...(link.ref ? { ref: link.ref } : {}),
600
+ ...(link.include?.length ? { include: link.include } : {}),
601
+ })),
602
+ null,
603
+ 2,
604
+ )}\n\`\`\`\n\nBuilder is the source of truth for repo/code design-system indexing. The action creates one local selectable proxy design system for Slides flows. If Builder is not connected, stop and tell me to connect Builder (free tier available) from Settings.`,
489
605
  );
490
606
  }
491
607
 
@@ -610,6 +726,7 @@ export function DesignSystemSetup({
610
726
  onComplete,
611
727
  t,
612
728
  updateSystemMutation,
729
+ indexSystemMutation,
613
730
  existingDs,
614
731
  ]);
615
732
 
@@ -651,7 +768,16 @@ export function DesignSystemSetup({
651
768
  </div>
652
769
 
653
770
  {editingId && existingDs?.builder ? (
654
- <BuilderSourceStatus builder={existingDs.builder} />
771
+ <BuilderSourceStatus
772
+ builder={existingDs.builder}
773
+ onSync={
774
+ existingDs.builder.githubSources?.length &&
775
+ existingDs.builder.sourceKind !== "mixed"
776
+ ? handleSync
777
+ : undefined
778
+ }
779
+ syncing={syncSystemMutation.isPending}
780
+ />
655
781
  ) : null}
656
782
 
657
783
  {!editingId && (
@@ -895,8 +1021,28 @@ export function DesignSystemSetup({
895
1021
  {t("designSystemSetup.add")}
896
1022
  </Button>
897
1023
  </div>
1024
+ <div className="grid gap-2 sm:grid-cols-2">
1025
+ <Input
1026
+ value={githubRef}
1027
+ onChange={(e) => setGithubRef(e.target.value)}
1028
+ placeholder={t("designSystemSetup.githubRef")}
1029
+ aria-label={t("designSystemSetup.githubRef")}
1030
+ className="bg-accent border-border text-foreground placeholder:text-muted-foreground"
1031
+ />
1032
+ <Input
1033
+ value={githubPaths}
1034
+ onChange={(e) => setGithubPaths(e.target.value)}
1035
+ placeholder={t("designSystemSetup.githubPaths")}
1036
+ aria-label={t("designSystemSetup.githubPaths")}
1037
+ className="bg-accent border-border text-foreground placeholder:text-muted-foreground"
1038
+ />
1039
+ </div>
898
1040
  <TagList
899
- items={githubLinks.map((l) => l.url)}
1041
+ items={githubLinks.map((l) =>
1042
+ [l.url, l.ref, l.include?.join(", ")]
1043
+ .filter(Boolean)
1044
+ .join(" · "),
1045
+ )}
900
1046
  onRemove={(i) =>
901
1047
  setGithubLinks((p) => p.filter((_, j) => j !== i))
902
1048
  }
@@ -1253,7 +1399,15 @@ function SourceAccordionRow({
1253
1399
  );
1254
1400
  }
1255
1401
 
1256
- function BuilderSourceStatus({ builder }: { builder: BuilderSourceDetails }) {
1402
+ function BuilderSourceStatus({
1403
+ builder,
1404
+ onSync,
1405
+ syncing = false,
1406
+ }: {
1407
+ builder: BuilderSourceDetails;
1408
+ onSync?: () => void;
1409
+ syncing?: boolean;
1410
+ }) {
1257
1411
  const t = useT();
1258
1412
  const docs = builder.docCount ?? builder.docs?.length ?? 0;
1259
1413
  const tokens = Object.keys(builder.tokenValues ?? {}).length;
@@ -1357,20 +1511,39 @@ function BuilderSourceStatus({ builder }: { builder: BuilderSourceDetails }) {
1357
1511
  {statusTitle}
1358
1512
  </div>
1359
1513
  </div>
1360
- {builder.builderUrl ? (
1361
- <div className="mt-3 border-t border-border pt-3">
1362
- <a
1363
- href={withBuilderUtmTrackingParams(builder.builderUrl, {
1364
- campaign: "product",
1365
- content: "design_system_intelligence",
1366
- })}
1367
- target="_blank"
1368
- rel="noreferrer"
1369
- className="inline-flex items-center gap-1.5 text-xs font-medium text-foreground underline-offset-4 hover:underline"
1370
- >
1371
- {t("designSystemSetup.sourceOpenInBuilder")}
1372
- <IconExternalLink className="size-3.5" />
1373
- </a>
1514
+ {builder.builderUrl || onSync ? (
1515
+ <div className="mt-3 flex flex-wrap items-center gap-3 border-t border-border pt-3">
1516
+ {builder.builderUrl ? (
1517
+ <a
1518
+ href={withBuilderUtmTrackingParams(builder.builderUrl, {
1519
+ campaign: "product",
1520
+ content: "design_system_intelligence",
1521
+ })}
1522
+ target="_blank"
1523
+ rel="noreferrer"
1524
+ className="inline-flex items-center gap-1.5 text-xs font-medium text-foreground underline-offset-4 hover:underline"
1525
+ >
1526
+ {t("designSystemSetup.sourceOpenInBuilder")}
1527
+ <IconExternalLink className="size-3.5" />
1528
+ </a>
1529
+ ) : null}
1530
+ {onSync ? (
1531
+ <Button
1532
+ type="button"
1533
+ variant="ghost"
1534
+ size="sm"
1535
+ onClick={onSync}
1536
+ disabled={syncing}
1537
+ className="h-7 gap-1.5 px-2 text-xs"
1538
+ >
1539
+ <IconRefresh
1540
+ className={cn("size-3.5", syncing && "animate-spin")}
1541
+ />
1542
+ {syncing
1543
+ ? t("designSystemSetup.syncingSource")
1544
+ : t("designSystemSetup.syncSource")}
1545
+ </Button>
1546
+ ) : null}
1374
1547
  </div>
1375
1548
  ) : null}
1376
1549
  </section>
@@ -1,12 +1,24 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { IconLoader2, IconPlus, IconTextSize } from "@tabler/icons-react";
2
+ import {
3
+ IconLoader2,
4
+ IconPlus,
5
+ IconTextSize,
6
+ IconTransitionRight,
7
+ } from "@tabler/icons-react";
3
8
  import { useEffect, useRef, useState } from "react";
4
9
 
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuTrigger,
15
+ } from "@/components/ui/dropdown-menu";
5
16
  import {
6
17
  Tooltip,
7
18
  TooltipContent,
8
19
  TooltipTrigger,
9
20
  } from "@/components/ui/tooltip";
21
+ import type { Slide } from "@/context/DeckContext";
10
22
  import { useAgentGenerating } from "@/hooks/use-agent-generating";
11
23
  import { cn } from "@/lib/utils";
12
24
 
@@ -19,6 +31,15 @@ const IDLE_CLASS =
19
31
  const ACTIVE_CLASS = "bg-accent text-foreground";
20
32
  const DIVIDER_CLASS = "mx-1 h-4 w-px shrink-0 bg-border";
21
33
 
34
+ type SlideTransition = NonNullable<Slide["transition"]>;
35
+
36
+ const TRANSITIONS: { value: SlideTransition; labelKey: string }[] = [
37
+ { value: "instant", labelKey: "editorToolbar.transition_instant" },
38
+ { value: "fade", labelKey: "editorToolbar.transition_fade" },
39
+ { value: "slide", labelKey: "editorToolbar.transition_slide" },
40
+ { value: "zoom", labelKey: "editorToolbar.transition_zoom" },
41
+ ];
42
+
22
43
  /**
23
44
  * Add slide, undo, redo, and add-text-box — the actions that stay put
24
45
  * regardless of what is selected. Rendered at the head of the contextual
@@ -36,6 +57,8 @@ export function EditorActionCluster({
36
57
  onDuplicateCurrentSlide,
37
58
  textBoxMode,
38
59
  onToggleTextBoxMode,
60
+ slideTransition,
61
+ onChangeSlideTransition,
39
62
  className,
40
63
  }: {
41
64
  deckId: string;
@@ -49,12 +72,19 @@ export function EditorActionCluster({
49
72
  onDuplicateCurrentSlide?: () => void;
50
73
  textBoxMode?: boolean;
51
74
  onToggleTextBoxMode?: () => void;
75
+ slideTransition?: Slide["transition"];
76
+ onChangeSlideTransition?: (transition: SlideTransition) => void;
52
77
  className?: string;
53
78
  }) {
54
79
  const t = useT();
55
80
  const { generating, submit: agentSubmit } = useAgentGenerating();
56
81
  const [addSlideOpen, setAddSlideOpen] = useState(false);
57
82
  const addSlideRef = useRef<HTMLButtonElement>(null);
83
+ // "none" is a legacy alias the presentation view already treats as instant.
84
+ const activeTransition: SlideTransition =
85
+ !slideTransition || slideTransition === "none"
86
+ ? "instant"
87
+ : slideTransition;
58
88
 
59
89
  useEffect(() => {
60
90
  if (!generating) onAddSlideGeneratingChange?.(false);
@@ -125,6 +155,48 @@ export function EditorActionCluster({
125
155
  </Tooltip>
126
156
  </>
127
157
  )}
158
+
159
+ {onChangeSlideTransition && currentSlideId && (
160
+ <>
161
+ <div className={DIVIDER_CLASS} />
162
+ <DropdownMenu>
163
+ <Tooltip>
164
+ <TooltipTrigger asChild>
165
+ <DropdownMenuTrigger asChild>
166
+ <button
167
+ type="button"
168
+ aria-label={t("editorToolbar.transition")}
169
+ className={cn(
170
+ BUTTON_CLASS,
171
+ activeTransition === "instant"
172
+ ? IDLE_CLASS
173
+ : ACTIVE_CLASS,
174
+ )}
175
+ >
176
+ <IconTransitionRight className="size-4" />
177
+ </button>
178
+ </DropdownMenuTrigger>
179
+ </TooltipTrigger>
180
+ <TooltipContent>{t("editorToolbar.transition")}</TooltipContent>
181
+ </Tooltip>
182
+ <DropdownMenuContent align="start" className="w-40">
183
+ {TRANSITIONS.map((transition) => (
184
+ <DropdownMenuItem
185
+ key={transition.value}
186
+ onSelect={() => onChangeSlideTransition(transition.value)}
187
+ className={
188
+ activeTransition === transition.value
189
+ ? "bg-accent text-accent-foreground"
190
+ : undefined
191
+ }
192
+ >
193
+ {t(transition.labelKey)}
194
+ </DropdownMenuItem>
195
+ ))}
196
+ </DropdownMenuContent>
197
+ </DropdownMenu>
198
+ </>
199
+ )}
128
200
  </div>
129
201
  );
130
202
  }
@@ -17,8 +17,7 @@ import {
17
17
  import { CSS } from "@dnd-kit/utilities";
18
18
  import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
19
19
  import { hashSlideContent, type DeckFitState } from "@shared/slide-fit";
20
- import { useRef, useEffect } from "react";
21
- import { useCallback } from "react";
20
+ import { useCallback, useEffect, useRef, useState } from "react";
22
21
 
23
22
  import SlideRenderer from "@/components/deck/SlideRenderer";
24
23
  import type { SlideOverflowInfo } from "@/components/deck/SlideRenderer";
@@ -34,6 +33,7 @@ import { getAspectRatioDims, type AspectRatio } from "@/lib/aspect-ratios";
34
33
  import { TAB_ID } from "@/lib/tab-id";
35
34
 
36
35
  import type { DesignSystemData } from "../../../shared/api";
36
+ import { isSlideTextEditingTarget } from "./slide-text-targets";
37
37
 
38
38
  interface EditorSidebarProps {
39
39
  slides: Slide[];
@@ -198,7 +198,12 @@ function SortableSlideThumb({
198
198
  type="button"
199
199
  {...(readOnly ? {} : attributes)}
200
200
  {...(readOnly ? {} : listeners)}
201
- onClick={onSelect}
201
+ onClick={(event) => {
202
+ // Safari does not focus a button on click, and the slide copy/paste
203
+ // and delete shortcuts are scoped to a focused thumbnail.
204
+ event.currentTarget.focus();
205
+ onSelect();
206
+ }}
202
207
  onFocus={onSelect}
203
208
  aria-label={t("editorSidebar.selectSlide", { number: index + 1 })}
204
209
  aria-current={isActive ? "true" : undefined}
@@ -317,6 +322,7 @@ export default function EditorSidebar({
317
322
  generatingSlideSelected = false,
318
323
  onSelectGeneratingSlide,
319
324
  }: EditorSidebarProps) {
325
+ const [thumbnailListScrolled, setThumbnailListScrolled] = useState(false);
320
326
  const slideButtonRefs = useRef(new Map<string, HTMLButtonElement>());
321
327
  const measurementsRef = useRef(
322
328
  new Map<
@@ -391,6 +397,10 @@ export default function EditorSidebar({
391
397
  measurementsRef.current.clear();
392
398
  }, [deckId, aspectRatio]);
393
399
 
400
+ useEffect(() => {
401
+ setThumbnailListScrolled(false);
402
+ }, [deckId]);
403
+
394
404
  useEffect(() => {
395
405
  return () => {
396
406
  if (writeTimerRef.current) clearTimeout(writeTimerRef.current);
@@ -419,12 +429,20 @@ export default function EditorSidebar({
419
429
  useEffect(() => {
420
430
  const handleKeyDown = (e: KeyboardEvent) => {
421
431
  if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
422
- // Don't intercept if user is typing in an input/textarea or contenteditable
432
+ // Text editing and selected canvas objects own arrow keys. The marker
433
+ // query also covers a transient focus loss after a native selection.
423
434
  const tag = (e.target as HTMLElement)?.tagName;
424
435
  if (
425
436
  tag === "INPUT" ||
426
437
  tag === "TEXTAREA" ||
427
- (e.target as HTMLElement)?.isContentEditable
438
+ isSlideTextEditingTarget(
439
+ e.target,
440
+ document.activeElement,
441
+ document.querySelector(
442
+ '[contenteditable="true"], [data-editing-block="true"]',
443
+ ),
444
+ ) ||
445
+ document.querySelector('[data-slide-element-selected="true"]')
428
446
  )
429
447
  return;
430
448
 
@@ -454,39 +472,51 @@ export default function EditorSidebar({
454
472
 
455
473
  return (
456
474
  <div className="flex h-full min-h-0 w-48 flex-shrink-0 flex-col bg-background sm:w-52">
457
- <div className="relative min-h-0 flex-1 space-y-1 overflow-y-auto overscroll-contain p-2">
458
- <SortableContext
459
- items={slides.map((s) => s.id)}
460
- strategy={verticalListSortingStrategy}
475
+ <div
476
+ className="relative min-h-0 flex-1"
477
+ data-slides-thumbnail-scroll={
478
+ thumbnailListScrolled ? "scrolled" : "top"
479
+ }
480
+ >
481
+ <div
482
+ className="h-full min-h-0 space-y-1 overflow-y-auto overscroll-contain p-2"
483
+ onScroll={(event) => {
484
+ setThumbnailListScrolled(event.currentTarget.scrollTop > 1);
485
+ }}
461
486
  >
462
- {slides.map((slide, index) => (
463
- <SortableSlideThumb
464
- key={slide.id}
465
- slide={slide}
466
- index={index}
467
- isActive={slide.id === activeSlideId}
468
- onSelect={() => onSelectSlide(slide.id)}
469
- readOnly={readOnly}
470
- registerButtonRef={registerSlideButton}
471
- presenceUsers={slidePresence?.get(slide.id) ?? []}
487
+ <SortableContext
488
+ items={slides.map((s) => s.id)}
489
+ strategy={verticalListSortingStrategy}
490
+ >
491
+ {slides.map((slide, index) => (
492
+ <SortableSlideThumb
493
+ key={slide.id}
494
+ slide={slide}
495
+ index={index}
496
+ isActive={slide.id === activeSlideId}
497
+ onSelect={() => onSelectSlide(slide.id)}
498
+ readOnly={readOnly}
499
+ registerButtonRef={registerSlideButton}
500
+ presenceUsers={slidePresence?.get(slide.id) ?? []}
501
+ aspectRatio={aspectRatio}
502
+ designSystem={designSystem}
503
+ aiEditing={aiEditedSlideIds.has(slide.id)}
504
+ onOverflowChange={(info) =>
505
+ handleSlideOverflowChange(slide, info)
506
+ }
507
+ />
508
+ ))}
509
+ </SortableContext>
510
+ {generatingSlide && (
511
+ <GeneratingSlideSkeleton
512
+ index={generatingSlide.index}
472
513
  aspectRatio={aspectRatio}
473
514
  designSystem={designSystem}
474
- aiEditing={aiEditedSlideIds.has(slide.id)}
475
- onOverflowChange={(info) =>
476
- handleSlideOverflowChange(slide, info)
477
- }
515
+ selected={generatingSlideSelected}
516
+ onSelect={onSelectGeneratingSlide}
478
517
  />
479
- ))}
480
- </SortableContext>
481
- {generatingSlide && (
482
- <GeneratingSlideSkeleton
483
- index={generatingSlide.index}
484
- aspectRatio={aspectRatio}
485
- designSystem={designSystem}
486
- selected={generatingSlideSelected}
487
- onSelect={onSelectGeneratingSlide}
488
- />
489
- )}
518
+ )}
519
+ </div>
490
520
  </div>
491
521
  </div>
492
522
  );