@agent-native/core 0.145.8 → 0.146.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 (155) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +41 -11
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  4. package/corpus/templates/analytics/actions/compose-dashboard.ts +2 -27
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +18 -3
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +75 -24
  7. package/corpus/templates/analytics/actions/mutate-dashboard.ts +16 -24
  8. package/corpus/templates/analytics/actions/rename-dashboard.ts +2 -24
  9. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +2 -24
  10. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +2 -25
  11. package/corpus/templates/analytics/actions/update-dashboard.ts +4 -32
  12. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -40
  13. package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
  14. package/corpus/templates/analytics/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-comm.md +6 -0
  15. package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
  16. package/corpus/templates/analytics/netlify.toml +7 -0
  17. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
  18. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +450 -0
  19. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +2 -2
  20. package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
  21. package/corpus/templates/analytics/server/lib/dashboard-collab-sync.ts +77 -0
  22. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +2 -23
  23. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
  24. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
  25. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
  26. package/corpus/templates/analytics/server/plugins/db.ts +42 -0
  27. package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
  28. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +18 -4
  29. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +41 -11
  30. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  31. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +41 -11
  32. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  33. package/corpus/templates/brain/server/lib/connectors.ts +38 -14
  34. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +41 -11
  35. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  36. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
  37. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  38. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +41 -11
  39. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  40. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  41. package/corpus/templates/clips/changelog/2026-08-07-fixed-trimmed-clips-durations-so-the-main-player-display-mat.md +6 -0
  42. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +1 -1
  43. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  44. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +41 -11
  45. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  46. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +41 -11
  47. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  48. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +41 -11
  49. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  50. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +41 -11
  51. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  52. package/corpus/templates/dispatch/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-disp.md +6 -0
  53. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +41 -11
  54. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  55. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +59 -22
  56. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +41 -11
  57. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  58. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +41 -11
  59. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  60. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +41 -11
  61. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  62. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +41 -11
  63. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  64. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +10 -1
  65. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +10 -3
  66. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +41 -11
  67. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  68. package/corpus/templates/slides/actions/generate-image-api.ts +123 -0
  69. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -2
  70. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
  71. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
  72. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
  73. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
  74. package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
  75. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  76. package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
  77. package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
  78. package/corpus/templates/slides/app/routes/slide.tsx +10 -5
  79. package/corpus/templates/slides/changelog/2026-08-07-deleting-a-deck-no-longer-freezes-the-slides-browser.md +6 -0
  80. package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
  81. package/corpus/templates/slides/changelog/2026-08-07-slides-verify-generated-images-are-persisted-on-the-target-s.md +6 -0
  82. package/corpus/templates/slides/server/db/schema.ts +1 -0
  83. package/corpus/templates/slides/server/handlers/share.ts +51 -3
  84. package/corpus/templates/slides/server/lib/slide-image-insertion.ts +141 -0
  85. package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
  86. package/corpus/templates/slides/server/plugins/db.ts +5 -0
  87. package/corpus/templates/slides/shared/api.ts +2 -0
  88. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +41 -11
  89. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  90. package/dist/cli/code-agent-executor.js +8 -3
  91. package/dist/client/AgentPanel.d.ts +10 -4
  92. package/dist/client/AgentPanel.js +29 -82
  93. package/dist/client/ConnectBuilderCard.d.ts +7 -7
  94. package/dist/client/ConnectBuilderCard.js +45 -20
  95. package/dist/collab/awareness.d.ts +2 -2
  96. package/dist/config.d.ts +9 -2
  97. package/dist/config.js +38 -2
  98. package/dist/db/client.d.ts +13 -1
  99. package/dist/db/client.js +76 -2
  100. package/dist/db/create-get-db.js +2 -2
  101. package/dist/db/migrations.js +22 -4
  102. package/dist/deploy/build.d.ts +1 -0
  103. package/dist/deploy/build.js +32 -23
  104. package/dist/notifications/routes.d.ts +3 -3
  105. package/dist/progress/routes.d.ts +1 -1
  106. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  107. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  108. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  109. package/dist/scripts/docs/search.js +3 -2
  110. package/dist/server/agent-chat/prompt-resources.js +4 -3
  111. package/dist/server/agents-bundle.d.ts +20 -3
  112. package/dist/server/agents-bundle.js +57 -30
  113. package/dist/server/better-auth-instance.js +2 -2
  114. package/dist/server/builder-browser.d.ts +34 -0
  115. package/dist/server/builder-browser.js +179 -0
  116. package/dist/server/core-routes-plugin.d.ts +5 -0
  117. package/dist/server/core-routes-plugin.js +24 -8
  118. package/dist/server/index.d.ts +1 -1
  119. package/dist/server/index.js +1 -1
  120. package/dist/shared/cache-control.d.ts +2 -2
  121. package/dist/shared/cache-control.js +2 -2
  122. package/dist/shared/runtime-config.js +1 -1
  123. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
  124. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  125. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
  126. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  127. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
  128. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  129. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
  130. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  131. package/dist/vite/agent-native-config-loader.d.ts +10 -0
  132. package/dist/vite/agent-native-config-loader.js +56 -0
  133. package/dist/vite/agents-bundle-plugin.d.ts +5 -1
  134. package/dist/vite/agents-bundle-plugin.js +29 -6
  135. package/dist/vite/client.d.ts +4 -2
  136. package/dist/vite/client.js +17 -50
  137. package/dist/vite/index.d.ts +2 -2
  138. package/dist/vite/index.js +1 -1
  139. package/docs/content/agent-native-config.mdx +43 -17
  140. package/docs/content/drop-in-agent.mdx +3 -3
  141. package/docs/content/environment-variables.mdx +1 -1
  142. package/docs/content/getting-started-actions.mdx +1 -2
  143. package/docs/content/getting-started-pages.mdx +1 -2
  144. package/docs/content/getting-started.mdx +0 -4
  145. package/docs/content/what-is-agent-native.mdx +4 -4
  146. package/package.json +1 -1
  147. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
  148. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  149. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
  150. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  151. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
  152. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  153. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
  154. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  155. package/docs/content/getting-started-database.mdx +0 -173
@@ -38,6 +38,8 @@ import type { Slide } from "@/context/DeckContext";
38
38
  import type { AspectRatio } from "@/lib/aspect-ratios";
39
39
  import { TAB_ID } from "@/lib/tab-id";
40
40
 
41
+ import type { DesignSystemData } from "../../../shared/api";
42
+
41
43
  interface EditorSidebarProps {
42
44
  slides: Slide[];
43
45
  activeSlideId: string;
@@ -53,6 +55,8 @@ interface EditorSidebarProps {
53
55
  recentEdits?: AttributedRecentEdit[];
54
56
  /** Deck aspect ratio (defaults to 16:9 when omitted) */
55
57
  aspectRatio?: AspectRatio;
58
+ /** Active deck design system used by slide content tokens. */
59
+ designSystem?: DesignSystemData;
56
60
  /** The next slide while the agent is preparing its HTML. */
57
61
  generatingSlide?: { index: number; content?: string | null };
58
62
  generatingSlideSelected?: boolean;
@@ -154,6 +158,7 @@ function SortableSlideThumb({
154
158
  registerButtonRef,
155
159
  presenceUsers = [],
156
160
  aspectRatio,
161
+ designSystem,
157
162
  onOverflowChange,
158
163
  readOnly = false,
159
164
  }: {
@@ -167,6 +172,7 @@ function SortableSlideThumb({
167
172
  registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void;
168
173
  presenceUsers?: CollabUser[];
169
174
  aspectRatio?: AspectRatio;
175
+ designSystem?: DesignSystemData;
170
176
  onOverflowChange: (info: SlideOverflowInfo) => void;
171
177
  }) {
172
178
  const t = useT();
@@ -234,6 +240,7 @@ function SortableSlideThumb({
234
240
  <SlideRenderer
235
241
  slide={slide}
236
242
  aspectRatio={aspectRatio}
243
+ designSystem={designSystem}
237
244
  onOverflowChange={onOverflowChange}
238
245
  />
239
246
  </div>
@@ -290,12 +297,14 @@ function SortableSlideThumb({
290
297
  function GeneratingSlideSkeleton({
291
298
  index,
292
299
  aspectRatio,
300
+ designSystem,
293
301
  content,
294
302
  selected,
295
303
  onSelect,
296
304
  }: {
297
305
  index: number;
298
306
  aspectRatio?: AspectRatio;
307
+ designSystem?: DesignSystemData;
299
308
  content?: string | null;
300
309
  selected?: boolean;
301
310
  onSelect?: () => void;
@@ -319,6 +328,7 @@ function GeneratingSlideSkeleton({
319
328
  <GeneratingSlidePreview
320
329
  content={content}
321
330
  aspectRatio={aspectRatio}
331
+ designSystem={designSystem}
322
332
  thumbnail
323
333
  />
324
334
  </div>
@@ -338,6 +348,7 @@ export default function EditorSidebar({
338
348
  slidePresence,
339
349
  recentEdits,
340
350
  aspectRatio,
351
+ designSystem,
341
352
  generatingSlide,
342
353
  generatingSlideSelected = false,
343
354
  onSelectGeneratingSlide,
@@ -508,6 +519,7 @@ export default function EditorSidebar({
508
519
  registerButtonRef={registerSlideButton}
509
520
  presenceUsers={slidePresence?.get(slide.id) ?? []}
510
521
  aspectRatio={aspectRatio}
522
+ designSystem={designSystem}
511
523
  onOverflowChange={(info) =>
512
524
  handleSlideOverflowChange(slide, info)
513
525
  }
@@ -518,6 +530,7 @@ export default function EditorSidebar({
518
530
  <GeneratingSlideSkeleton
519
531
  index={generatingSlide.index}
520
532
  aspectRatio={aspectRatio}
533
+ designSystem={designSystem}
521
534
  content={generatingSlide.content}
522
535
  selected={generatingSlideSelected}
523
536
  onSelect={onSelectGeneratingSlide}
@@ -7,6 +7,8 @@ import type { Slide } from "@/context/DeckContext";
7
7
  import type { AspectRatio } from "@/lib/aspect-ratios";
8
8
  import { cn } from "@/lib/utils";
9
9
 
10
+ import type { DesignSystemData } from "../../../shared/api";
11
+
10
12
  function SlideLoadingArtwork() {
11
13
  return (
12
14
  <div className="absolute inset-0 flex flex-col justify-center gap-4 bg-muted/30 p-[14%]">
@@ -25,11 +27,13 @@ export default function GeneratingSlidePreview({
25
27
  aspectRatio,
26
28
  thumbnail = true,
27
29
  className,
30
+ designSystem,
28
31
  }: {
29
32
  content?: string | null;
30
33
  aspectRatio?: AspectRatio;
31
34
  thumbnail?: boolean;
32
35
  className?: string;
36
+ designSystem?: DesignSystemData;
33
37
  }) {
34
38
  const t = useT();
35
39
  const cssRatio = (aspectRatio ?? "16:9").replace(":", " / ");
@@ -55,6 +59,7 @@ export default function GeneratingSlidePreview({
55
59
  slide={previewSlide}
56
60
  aspectRatio={aspectRatio}
57
61
  thumbnail={thumbnail}
62
+ designSystem={designSystem}
58
63
  className="h-full w-full"
59
64
  />
60
65
  ) : (
@@ -80,7 +80,7 @@ export function buildImageGenerationContext({
80
80
  }
81
81
 
82
82
  contextParts.push(
83
- '\nGenerate 3 variations. Show each as an inline rendered image preview using markdown image syntax (![Variation 1](url)), not a plain text link — the chat renders "![]()" as an actual image but "[]()" as a bare link. Let the user pick their favorite, then insert the chosen generated image into the slide content in the right place.',
83
+ '\nGenerate 3 preview-only variations. Show each as an inline rendered image preview using markdown image syntax (![Variation 1](url)), not a plain text link — the chat renders "![]()" as an actual image but "[]()" as a bare link. After the user chooses one, place that preview URL with update-slide and then get-deck to verify the persisted slide HTML contains it before claiming success. For a direct one-image request instead, call `generate-image-api` with insertIntoSlide: true plus deckId and slideId; claim it was added only if the action returns inserted: true.',
84
84
  );
85
85
 
86
86
  return contextParts.join("\n");
@@ -22,6 +22,7 @@ import {
22
22
  resolveSlideAnimationElement,
23
23
  } from "@/lib/slide-animation-elements";
24
24
 
25
+ import type { DesignSystemData } from "../../../shared/api";
25
26
  import { openPresentChannel, type PresentMessage } from "./present-channel";
26
27
 
27
28
  interface PresentationViewProps {
@@ -29,6 +30,7 @@ interface PresentationViewProps {
29
30
  deckId: string;
30
31
  startIndex?: number;
31
32
  aspectRatio?: AspectRatio;
33
+ designSystem?: DesignSystemData;
32
34
  }
33
35
 
34
36
  // ─── Element animation helpers ────────────────────────────────────────────────
@@ -182,6 +184,7 @@ export default function PresentationView({
182
184
  deckId,
183
185
  startIndex = 0,
184
186
  aspectRatio,
187
+ designSystem,
185
188
  }: PresentationViewProps) {
186
189
  const t = useT();
187
190
  const safeSlides = useMemo(
@@ -534,6 +537,7 @@ export default function PresentationView({
534
537
  slide={safeSlides[prevIndex]}
535
538
  thumbnail={false}
536
539
  aspectRatio={aspectRatio}
540
+ designSystem={designSystem}
537
541
  />
538
542
  </div>
539
543
  )}
@@ -548,6 +552,7 @@ export default function PresentationView({
548
552
  slide={displaySlide}
549
553
  thumbnail={false}
550
554
  aspectRatio={aspectRatio}
555
+ designSystem={designSystem}
551
556
  />
552
557
  </div>
553
558
 
@@ -6,6 +6,7 @@ import SlideRenderer from "@/components/deck/SlideRenderer";
6
6
  import type { Slide } from "@/context/DeckContext";
7
7
  import type { AspectRatio } from "@/lib/aspect-ratios";
8
8
 
9
+ import type { DesignSystemData } from "../../../shared/api";
9
10
  import { openPresentChannel, type PresentMessage } from "./present-channel";
10
11
 
11
12
  interface PresenterViewProps {
@@ -13,6 +14,7 @@ interface PresenterViewProps {
13
14
  deckId: string;
14
15
  startIndex?: number;
15
16
  aspectRatio?: AspectRatio;
17
+ designSystem?: DesignSystemData;
16
18
  }
17
19
 
18
20
  function formatElapsed(seconds: number) {
@@ -26,6 +28,7 @@ export default function PresenterView({
26
28
  deckId,
27
29
  startIndex = 0,
28
30
  aspectRatio,
31
+ designSystem,
29
32
  }: PresenterViewProps) {
30
33
  const t = useT();
31
34
  const [index, setIndex] = useState(startIndex);
@@ -136,6 +139,7 @@ export default function PresenterView({
136
139
  slide={current}
137
140
  thumbnail
138
141
  aspectRatio={aspectRatio}
142
+ designSystem={designSystem}
139
143
  />
140
144
  )}
141
145
  </div>
@@ -149,6 +153,7 @@ export default function PresenterView({
149
153
  slide={next}
150
154
  thumbnail
151
155
  aspectRatio={aspectRatio}
156
+ designSystem={designSystem}
152
157
  />
153
158
  </div>
154
159
  ) : (
@@ -1128,6 +1128,7 @@ export default function DeckEditor() {
1128
1128
  slidePresence={slidePresence}
1129
1129
  recentEdits={deckRecentEdits}
1130
1130
  aspectRatio={deck.aspectRatio}
1131
+ designSystem={designSystem}
1131
1132
  generatingSlide={
1132
1133
  generatingSlideVisible
1133
1134
  ? {
@@ -1166,6 +1167,7 @@ export default function DeckEditor() {
1166
1167
  <GeneratingSlidePreview
1167
1168
  content={streamedGeneratingSlideContent}
1168
1169
  aspectRatio={deck.aspectRatio}
1170
+ designSystem={designSystem}
1169
1171
  thumbnail={false}
1170
1172
  />
1171
1173
  </div>
@@ -1181,6 +1183,7 @@ export default function DeckEditor() {
1181
1183
  <GeneratingSlidePreview
1182
1184
  content={streamedGeneratingSlideContent}
1183
1185
  aspectRatio={deck.aspectRatio}
1186
+ designSystem={designSystem}
1184
1187
  thumbnail={false}
1185
1188
  />
1186
1189
  </div>
@@ -7,6 +7,7 @@ import PresentationView from "@/components/presentation/PresentationView";
7
7
  import PresenterView from "@/components/presentation/PresenterView";
8
8
  import { useDecks } from "@/context/DeckContext";
9
9
  import type { Deck } from "@/context/DeckContext";
10
+ import { useDeckDesignSystem } from "@/hooks/use-deck-design-system";
10
11
 
11
12
  export default function Presentation() {
12
13
  const { id } = useParams<{ id: string }>();
@@ -25,6 +26,7 @@ export default function Presentation() {
25
26
  const [searchParams] = useSearchParams();
26
27
  const contextDeck = getDeck(id || "");
27
28
  const deck = contextDeck ?? fallbackDeck;
29
+ const { designSystem } = useDeckDesignSystem(deck?.designSystemId);
28
30
 
29
31
  useEffect(() => {
30
32
  if (!id || loading || contextDeck) {
@@ -104,6 +106,7 @@ export default function Presentation() {
104
106
  deckId={id}
105
107
  startIndex={startSlide}
106
108
  aspectRatio={deck.aspectRatio}
109
+ designSystem={designSystem}
107
110
  />
108
111
  );
109
112
  }
@@ -7,6 +7,7 @@ import { useParams } from "react-router";
7
7
 
8
8
  import PresentationView from "@/components/presentation/PresentationView";
9
9
  import type { Slide } from "@/context/DeckContext";
10
+ import { mergeDesignSystemData } from "@/hooks/use-deck-design-system";
10
11
 
11
12
  interface SharedPresentationProps {
12
13
  initialDeck?: SharedDeckResponse | null;
@@ -81,6 +82,9 @@ export default function SharedPresentation({
81
82
  ...s,
82
83
  layout: s.layout as Slide["layout"],
83
84
  }));
85
+ const designSystem = deck.designSystem
86
+ ? mergeDesignSystemData(deck.designSystem)
87
+ : undefined;
84
88
 
85
89
  // Use a fake deckId that routes "exit" back to the share page itself
86
90
  return (
@@ -88,6 +92,7 @@ export default function SharedPresentation({
88
92
  slides={slides}
89
93
  deckId={`__shared__/${token}`}
90
94
  aspectRatio={deck.aspectRatio}
95
+ designSystem={designSystem}
91
96
  />
92
97
  );
93
98
  }
@@ -10,6 +10,7 @@ import { useSearchParams } from "react-router";
10
10
 
11
11
  import SlideRenderer from "@/components/deck/SlideRenderer";
12
12
  import type { Slide } from "@/context/DeckContext";
13
+ import { useDeckDesignSystem } from "@/hooks/use-deck-design-system";
13
14
  import messages from "@/i18n/en-US";
14
15
  import type { AspectRatio } from "@/lib/aspect-ratios";
15
16
 
@@ -42,8 +43,10 @@ export default function SlideRoute() {
42
43
  const [aspectRatio, setAspectRatio] = useState<AspectRatio | undefined>(
43
44
  undefined,
44
45
  );
46
+ const [designSystemId, setDesignSystemId] = useState<string | null>(null);
45
47
  const [error, setError] = useState<string | null>(null);
46
48
  const [loading, setLoading] = useState(true);
49
+ const { designSystem } = useDeckDesignSystem(designSystemId);
47
50
 
48
51
  const slideNumber =
49
52
  slideNumberParam !== null ? parseInt(slideNumberParam, 10) : null;
@@ -75,11 +78,11 @@ export default function SlideRoute() {
75
78
  return;
76
79
  }
77
80
 
78
- callAction<{ slides?: Slide[]; aspectRatio?: AspectRatio }>(
79
- "get-deck",
80
- { id: deckId },
81
- { method: "GET" },
82
- )
81
+ callAction<{
82
+ slides?: Slide[];
83
+ aspectRatio?: AspectRatio;
84
+ designSystemId?: string | null;
85
+ }>("get-deck", { id: deckId }, { method: "GET" })
83
86
  .then((deck) => {
84
87
  const slides: Slide[] = deck.slides ?? [];
85
88
  if (slides.length === 0) {
@@ -88,6 +91,7 @@ export default function SlideRoute() {
88
91
  const idx = Math.max(0, Math.min(slideIndex, slides.length - 1));
89
92
  setSlide(slides[idx]);
90
93
  setAspectRatio(deck.aspectRatio);
94
+ setDesignSystemId(deck.designSystemId ?? null);
91
95
  setLoading(false);
92
96
  })
93
97
  .catch((err: unknown) => {
@@ -123,6 +127,7 @@ export default function SlideRoute() {
123
127
  slide={slide}
124
128
  thumbnail={false}
125
129
  aspectRatio={aspectRatio}
130
+ designSystem={designSystem}
126
131
  />
127
132
 
128
133
  {inEmbed && (
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Deleting a deck no longer freezes the Slides browser
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Slide previews and Present now keep deck colors consistent
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Slides verify generated images are persisted on the target slide
@@ -54,6 +54,7 @@ export const deckShareLinks = table("deck_share_links", {
54
54
  title: text("title").notNull(),
55
55
  slides: text("slides").notNull(), // JSON array of slide snapshots
56
56
  aspectRatio: text("aspect_ratio"),
57
+ designSystemData: text("design_system_data"), // Share-safe token snapshot
57
58
  createdAt: text("created_at").notNull().default(now()),
58
59
  });
59
60
 
@@ -1,9 +1,14 @@
1
1
  import crypto from "crypto";
2
2
 
3
3
  import { readBody } from "@agent-native/core/server";
4
- import { assertAccess, ForbiddenError } from "@agent-native/core/sharing";
4
+ import {
5
+ assertAccess,
6
+ ForbiddenError,
7
+ resolveAccess,
8
+ } from "@agent-native/core/sharing";
5
9
  import { toSharedDeckSlide } from "@shared/api";
6
10
  import type {
11
+ DesignSystemData,
7
12
  ShareDeckRequest,
8
13
  ShareDeckResponse,
9
14
  SharedDeckResponse,
@@ -19,6 +24,12 @@ import {
19
24
 
20
25
  const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
21
26
 
27
+ interface DeckShareResource {
28
+ title?: string | null;
29
+ data: string;
30
+ designSystemId?: string | null;
31
+ }
32
+
22
33
  /**
23
34
  * POST /api/share
24
35
  * Persist a deck snapshot with a random token.
@@ -53,11 +64,13 @@ async function createShareLink(event: any, deckId: string) {
53
64
  const db = getDb();
54
65
  let storedDeck: any;
55
66
  let title = "Untitled";
67
+ let deckResource: DeckShareResource;
56
68
 
57
69
  try {
58
70
  const access = await assertAccess("deck", deckId, "admin");
59
- title = access.resource.title ?? "Untitled";
60
- storedDeck = JSON.parse(access.resource.data);
71
+ deckResource = access.resource as DeckShareResource;
72
+ title = deckResource.title ?? "Untitled";
73
+ storedDeck = JSON.parse(deckResource.data);
61
74
  } catch (err) {
62
75
  if (err instanceof ForbiddenError) {
63
76
  setResponseStatus(event, err.statusCode);
@@ -73,6 +86,29 @@ async function createShareLink(event: any, deckId: string) {
73
86
 
74
87
  const token = crypto.randomBytes(12).toString("base64url");
75
88
  const now = new Date().toISOString();
89
+ const designSystemId =
90
+ deckResource.designSystemId ?? storedDeck.designSystemId;
91
+ let designSystemData: string | null = null;
92
+
93
+ if (typeof designSystemId === "string" && designSystemId.trim()) {
94
+ const designSystemAccess = await resolveAccess(
95
+ "design-system",
96
+ designSystemId,
97
+ );
98
+ const rawData = designSystemAccess?.resource?.data;
99
+ if (typeof rawData === "string") {
100
+ try {
101
+ const parsed = JSON.parse(rawData);
102
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
103
+ designSystemData = JSON.stringify(parsed as DesignSystemData);
104
+ }
105
+ // coercion-ok: malformed optional style data keeps a valid deck shareable.
106
+ } catch {
107
+ // A malformed style record should not make an otherwise valid deck
108
+ // impossible to share; the presentation will use its default tokens.
109
+ }
110
+ }
111
+ }
76
112
 
77
113
  const slides = storedDeck.slides.map((slide: unknown, index: number) =>
78
114
  toSharedDeckSlide(slide, index),
@@ -83,6 +119,7 @@ async function createShareLink(event: any, deckId: string) {
83
119
  title: title || storedDeck.title || "Untitled",
84
120
  slides: JSON.stringify(slides),
85
121
  aspectRatio: storedDeck.aspectRatio ?? null,
122
+ designSystemData,
86
123
  createdAt: now,
87
124
  });
88
125
 
@@ -136,5 +173,16 @@ export const getSharedDeck = defineEventHandler(async (event) => {
136
173
  slides: JSON.parse(shared.slides),
137
174
  aspectRatio: shared.aspectRatio as SharedDeckResponse["aspectRatio"],
138
175
  };
176
+ if (shared.designSystemData) {
177
+ try {
178
+ const parsed = JSON.parse(shared.designSystemData);
179
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
180
+ response.designSystem = parsed as DesignSystemData;
181
+ }
182
+ // coercion-ok: malformed optional snapshots remain viewable with default tokens.
183
+ } catch {
184
+ // Keep legacy or malformed snapshots viewable with default tokens.
185
+ }
186
+ }
139
187
  return response;
140
188
  });
@@ -0,0 +1,141 @@
1
+ interface InsertSlideImageOptions {
2
+ alt?: string;
3
+ }
4
+
5
+ function cleanAlt(value: string | undefined): string {
6
+ return (value || "Uploaded image").replace(/\s+/g, " ").trim();
7
+ }
8
+
9
+ function escapeAttribute(value: string): string {
10
+ return value
11
+ .replace(/&/g, "&amp;")
12
+ .replace(/"/g, "&quot;")
13
+ .replace(/</g, "&lt;")
14
+ .replace(/>/g, "&gt;");
15
+ }
16
+
17
+ function decodeHtml(value: string): string {
18
+ return value
19
+ .replace(/&quot;/gi, '"')
20
+ .replace(/&#39;/gi, "'")
21
+ .replace(/&lt;/gi, "<")
22
+ .replace(/&gt;/gi, ">")
23
+ .replace(/&amp;/gi, "&");
24
+ }
25
+
26
+ function textContent(html: string): string {
27
+ return decodeHtml(html.replace(/<[^>]+>/g, " "))
28
+ .replace(/\s+/g, " ")
29
+ .trim();
30
+ }
31
+
32
+ function attribute(attributes: string, name: string): string | null {
33
+ const match = attributes.match(
34
+ new RegExp(`\\b${name}\\s*=\\s*(["'])([\\s\\S]*?)\\1`, "i"),
35
+ );
36
+ return match ? match[2] : null;
37
+ }
38
+
39
+ function hasStyleProperty(style: string, property: string): boolean {
40
+ return new RegExp(`(?:^|;)\\s*${property}\\s*:`, "i").test(style);
41
+ }
42
+
43
+ function appendImageStyle(baseStyle: string): string {
44
+ const declarations = [baseStyle.trim().replace(/;+\s*$/, "")].filter(Boolean);
45
+ if (!hasStyleProperty(baseStyle, "display"))
46
+ declarations.push("display: block");
47
+ if (!hasStyleProperty(baseStyle, "object-fit")) {
48
+ declarations.push("object-fit: cover");
49
+ }
50
+ if (!hasStyleProperty(baseStyle, "min-width"))
51
+ declarations.push("min-width: 0");
52
+ return declarations.length > 0 ? `${declarations.join("; ")};` : "";
53
+ }
54
+
55
+ function imageHtml(src: string, alt: string, style: string): string {
56
+ return `<img src="${escapeAttribute(src)}" alt="${escapeAttribute(alt)}" class="fmd-img-uploaded"${style ? ` style="${escapeAttribute(style)}"` : ""}>`;
57
+ }
58
+
59
+ /**
60
+ * Server-safe counterpart to the editor's image replacement helper. It uses
61
+ * deterministic string transforms because DOMParser is not available in the
62
+ * server runtime that executes actions.
63
+ */
64
+ export function insertImageIntoSlideHtml(
65
+ content: string,
66
+ src: string,
67
+ options: InsertSlideImageOptions = {},
68
+ ): string {
69
+ const openingTags = /<([a-z][\w:-]*)([^>]*)>/gi;
70
+ for (const match of content.matchAll(openingTags)) {
71
+ const className = attribute(match[2], "class") || "";
72
+ if (!/(?:^|\s)fmd-img-placeholder(?:\s|$)/.test(className)) continue;
73
+
74
+ const closingTag = new RegExp(`</${match[1]}\\s*>`, "gi");
75
+ closingTag.lastIndex = (match.index ?? 0) + match[0].length;
76
+ const closing = closingTag.exec(content);
77
+ if (!closing || match.index === undefined) continue;
78
+
79
+ const style = appendImageStyle(
80
+ attribute(match[2], "style") ||
81
+ "width: 100%; height: 100%; border-radius: 8px; object-fit: cover;",
82
+ );
83
+ const replacement = imageHtml(
84
+ src,
85
+ cleanAlt(
86
+ options.alt ||
87
+ textContent(
88
+ content.slice(match.index + match[0].length, closing.index),
89
+ ) ||
90
+ "Uploaded image",
91
+ ),
92
+ style,
93
+ );
94
+ return `${content.slice(0, match.index)}${replacement}${content.slice(closing.index + closing[0].length)}`;
95
+ }
96
+
97
+ const fullBleed = imageHtml(
98
+ src,
99
+ cleanAlt(options.alt),
100
+ "position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -1;",
101
+ );
102
+ const slideRoot =
103
+ /<([a-z][\w:-]*)([^>]*\bclass\s*=\s*(["'])[^"']*\bfmd-slide\b[^"']*\3[^>]*)>/i.exec(
104
+ content,
105
+ );
106
+ if (!slideRoot || slideRoot.index === undefined)
107
+ return `${fullBleed}${content}`;
108
+
109
+ let openingTag = slideRoot[0];
110
+ const style = attribute(slideRoot[2], "style") || "";
111
+ if (!hasStyleProperty(style, "position")) {
112
+ const nextStyle =
113
+ `${style.trim().replace(/;+\s*$/, "")}; position: relative;`.replace(
114
+ /^;\s*/,
115
+ "",
116
+ );
117
+ if (/\bstyle\s*=\s*(["'])/i.test(openingTag)) {
118
+ openingTag = openingTag.replace(
119
+ /\bstyle\s*=\s*(["'])[^"']*\1/i,
120
+ `style="${escapeAttribute(nextStyle)}"`,
121
+ );
122
+ } else {
123
+ openingTag = openingTag.replace(
124
+ />$/,
125
+ ` style="${escapeAttribute(nextStyle)}">`,
126
+ );
127
+ }
128
+ }
129
+ return `${content.slice(0, slideRoot.index)}${openingTag}${fullBleed}${content.slice(slideRoot.index + slideRoot[0].length)}`;
130
+ }
131
+
132
+ export function slideHtmlContainsImageSource(
133
+ html: string,
134
+ src: string,
135
+ ): boolean {
136
+ const images = /<img\b([^>]*)>/gi;
137
+ for (const match of html.matchAll(images)) {
138
+ if (decodeHtml(attribute(match[1], "src") || "") === src) return true;
139
+ }
140
+ return false;
141
+ }
@@ -62,6 +62,8 @@ Layout-fit workflow is strict. When the user asks to fix overflow, first call vi
62
62
 
63
63
  Fit means the main content fits the native content area. A small outer-wrapper spill is tolerated by the measurement, but cards, text, columns, and other visible content must fit. Never use zoom, transform: scale(), overflow: hidden/scroll, clipping, or a smaller-than-16px body font to hide overflow. Preserve manually positioned freeform objects and their data-slide-object-id values; repair normal-flow structure, copy, gaps, or slide padding instead. A successful action result must include the affected slide IDs; if it does not, report that no verified write occurred.
64
64
 
65
+ Image workflow is strict. For direct insertion, call generate-image-api with insertIntoSlide: true plus deckId and slideId. Claim that an image was added only when that action returns inserted: true; a preview URL or completed generation alone is not a slide edit. For preview-only variations, call generate-image-api without insertIntoSlide, then use update-slide to place the chosen URL and re-read the target with get-deck to confirm its persisted HTML contains that image source before claiming success.
66
+
65
67
  Provider-specific Slides actions are shortcuts, not limits. If a first-class action cannot express the exact Google Drive endpoint, file metadata field, export format, query, request body, pagination mode, payload shape, or API version needed, call provider-api-catalog and provider-api-docs as needed, then call provider-api-request against the real provider API. Use the raw provider API escape hatch instead of weakening the answer or claiming Slides cannot do something the underlying Google Drive API can do.
66
68
 
67
69
  Slides' Google Drive provider API uses the user's connected Google Docs OAuth account. The drive.file scope is intentionally limited to files the user selected or the app created. For large Drive file lists or metadata sweeps, pass stageAs and pagination options to provider-api-request, then use query-staged-dataset to count, filter, group, or project the staged rows.
@@ -230,6 +230,11 @@ const runSlidesMigrations = runMigrations(
230
230
  );
231
231
  CREATE INDEX IF NOT EXISTS uploaded_assets_owner_created_idx ON uploaded_assets (owner_email, created_at)`,
232
232
  },
233
+ {
234
+ version: 21,
235
+ name: "slides-share-design-system-snapshot",
236
+ sql: `ALTER TABLE deck_share_links ADD COLUMN IF NOT EXISTS design_system_data TEXT`,
237
+ },
233
238
  ],
234
239
  { table: "slides_migrations" },
235
240
  );
@@ -82,6 +82,8 @@ export interface SharedDeckResponse {
82
82
  title: string;
83
83
  slides: SharedDeckSlide[];
84
84
  aspectRatio?: import("./aspect-ratios").AspectRatio;
85
+ /** Resolved at share creation so public links keep the deck's styling. */
86
+ designSystem?: DesignSystemData;
85
87
  }
86
88
 
87
89
  export type SharedSlideTransition =