diff --git a/templates/slides/.agents/skills/create-deck/SKILL.md b/templates/slides/.agents/skills/create-deck/SKILL.md index fa1a5490c8..5841d444f7 100644 --- a/templates/slides/.agents/skills/create-deck/SKILL.md +++ b/templates/slides/.agents/skills/create-deck/SKILL.md @@ -48,12 +48,12 @@ pnpm action add-slide --deckId= --layout content --content "..." Every slide's `content` must use this exact outer div: ```html -
+
``` -Background is pure black (`bg-[#000000]`) — set by the renderer, not the slide HTML. +Background (`var(--ds-bg)`) — set by the renderer, not the slide HTML. ## Ready-to-Use Templates @@ -64,10 +64,10 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Title Slide ```html -
-
[LABEL OR DATE]
-

[TITLE]

-

[SUBTITLE OR PRESENTER]

+
+
[LABEL OR DATE]
+

[TITLE]

+

[SUBTITLE OR PRESENTER]

``` @@ -76,9 +76,9 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Section Divider ```html -
-
[SECTION NUMBER, e.g. 01]
-

[SECTION TITLE]

+
+
[SECTION NUMBER, e.g. 01]
+

[SECTION TITLE]

``` @@ -87,21 +87,21 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Content Slide (bullets) ```html -
-
[SECTION LABEL]
-

[SLIDE HEADING]

+
+
[SECTION LABEL]
+

[SLIDE HEADING]

- - [BULLET TEXT] + + [BULLET TEXT]
- - [BULLET TEXT] + + [BULLET TEXT]
- - [BULLET TEXT] + + [BULLET TEXT]
@@ -112,18 +112,18 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Two-Column Slide (text left, image/visual right) ```html -
-
[SECTION LABEL]
-

[HEADING]

+
+
[SECTION LABEL]
+

[HEADING]

- - [BULLET] + + [BULLET]
- - [BULLET] + + [BULLET]
[IMAGE DESCRIPTION]
@@ -136,10 +136,10 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Statement / Quote Slide ```html -
-
-

“[STATEMENT OR QUOTE]”

-

[SOURCE OR ATTRIBUTION]

+
+
+

“[STATEMENT OR QUOTE]”

+

[SOURCE OR ATTRIBUTION]

``` @@ -148,21 +148,21 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Metrics / Stats Slide ```html -
-
[SECTION LABEL]
-

[HEADING]

+
+
[SECTION LABEL]
+

[HEADING]

-
[METRIC]
-
[LABEL]
+
[METRIC]
+
[LABEL]
-
[METRIC]
-
[LABEL]
+
[METRIC]
+
[LABEL]
-
[METRIC]
-
[LABEL]
+
[METRIC]
+
[LABEL]
@@ -173,10 +173,10 @@ Copy and fill in the bracketed values. Use `\` to escape quotes inside the JSON ### Closing / CTA Slide ```html -
-
[LABEL, e.g. GET STARTED]
-

[CLOSING STATEMENT]

-

[CONTACT OR NEXT STEP]

+
+
[LABEL, e.g. GET STARTED]
+

[CLOSING STATEMENT]

+

[CONTACT OR NEXT STEP]

``` @@ -194,10 +194,10 @@ For definition-style bullets: ```html
- + - [Term] - — [description] + [Term] + — [description]
``` @@ -215,12 +215,12 @@ pnpm action create-deck --title "Product Vision 2025" --slides '[ { "id": "slide-1", "layout": "title", - "content": "
ANNUAL STRATEGY

Product Vision 2025

Engineering Leadership — Q1 2025

" + "content": "
ANNUAL STRATEGY

Product Vision 2025

Engineering Leadership — Q1 2025

" }, { "id": "slide-2", "layout": "content", - "content": "
OVERVIEW

Three Core Priorities

Ship the agent platform by March
Grow to 10k active teams
Reduce time-to-value to under 5 minutes
" + "content": "
OVERVIEW

Three Core Priorities

Ship the agent platform by March
Grow to 10k active teams
Reduce time-to-value to under 5 minutes
" } ]' ``` diff --git a/templates/slides/.agents/skills/design-systems/SKILL.md b/templates/slides/.agents/skills/design-systems/SKILL.md index c69a1335e7..6537e4e889 100644 --- a/templates/slides/.agents/skills/design-systems/SKILL.md +++ b/templates/slides/.agents/skills/design-systems/SKILL.md @@ -49,6 +49,15 @@ When generating slides, replace default values with design system tokens: - `#000000` background -> `colors.background` - `rgba(255,255,255,0.55)` -> `colors.textMuted` +## Retroactive application + +To apply a design system to an existing deck: +1. Call `apply-design-system --deckId= --designSystemId=` +2. That is all. The renderer reads the linked design system and + applies all tokens via CSS custom properties automatically. +Do not call get-deck or update-slide. Do not rewrite slide HTML. +The CSS cascade handles token application instantly. + ## Tweaks The Tweaks panel provides live CSS variable overrides: diff --git a/templates/slides/.agents/skills/slide-editing/SKILL.md b/templates/slides/.agents/skills/slide-editing/SKILL.md index 41d728995a..51f0e11c9d 100644 --- a/templates/slides/.agents/skills/slide-editing/SKILL.md +++ b/templates/slides/.agents/skills/slide-editing/SKILL.md @@ -23,24 +23,22 @@ All generated slides follow these conventions: | Element | Style | |---------|-------| -| Background | `bg-[#000000]` (pure black) | -| Font | `font-family: 'Poppins', sans-serif` on all text | -| Section labels | `font-size: 16px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; color: #00E5FF` | -| Headings | `font-size: 40px; font-weight: 900; color: #fff; line-height: 1.15; letter-spacing: -1px` | -| Title slides | `font-size: 54px; font-weight: 900` with `justify-content: center` | -| Bullet points | `●` character (8px, white), gap: 20px, font-size: 22px, color: rgba(255,255,255,0.85) | +| Background | `var(--ds-bg)` (from the linked design system) | +| Font | `font-family: var(--ds-heading-font), sans-serif` on all text | +| Section labels | `font-size: 16px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; color: var(--ds-accent)` | +| Headings | `font-size: 40px; font-weight: var(--ds-heading-weight); color: var(--ds-text); line-height: 1.15; letter-spacing: -1px` | +| Title slides | `font-size: 54px; font-weight: var(--ds-heading-weight)` with `justify-content: center` | +| Bullet points | `●` character (8px, white), gap: 20px, font-size: 22px, color: var(--ds-text) | | Sub-bullets | `○` (open circle), padding-left: 36px | -| Bold terms | `Term` + description in rgba(255,255,255,0.55) | -| Accent color | `#00E5FF` (cyan) for section labels, emphasis, highlights | +| Bold terms | `Term` + description in var(--ds-text-muted) | +| Accent color | `var(--ds-accent)` for section labels, emphasis, highlights | ## Updating a Slide To edit a slide's content: -1. **Get the deck**: `pnpm action get-deck --id=` -2. **Parse the JSON**, find the slide by ID -3. **Modify the content** HTML string -4. **Update the deck** via `PUT /api/decks/:id` with the full updated deck JSON +1. Use `pnpm action update-slide --deckId= --slideId= --find="" --replace=""` for surgical token edits +2. Use `pnpm action update-slide --deckId= --slideId= --fullContent=""` only for full slide rewrites ## Image Placeholders diff --git a/templates/slides/app/components/deck/DeckCard.tsx b/templates/slides/app/components/deck/DeckCard.tsx index cfa3e5ff1c..14e6019f72 100644 --- a/templates/slides/app/components/deck/DeckCard.tsx +++ b/templates/slides/app/components/deck/DeckCard.tsx @@ -18,6 +18,7 @@ import { } from "@/components/ui/dropdown-menu"; import type { Deck } from "@/context/DeckContext"; +import type { DesignSystemData } from "../../../shared/api"; import SlideRenderer from "./SlideRenderer"; interface DeckCardProps { @@ -27,6 +28,7 @@ interface DeckCardProps { onDuplicate: (id: string) => void; isDuplicating?: boolean; designSystemTitle?: string | null; + designSystem?: DesignSystemData; } export default function DeckCard({ @@ -36,6 +38,7 @@ export default function DeckCard({ onDuplicate, isDuplicating = false, designSystemTitle, + designSystem, }: DeckCardProps) { const t = useT(); const firstSlide = deck.slides?.[0]; @@ -96,6 +99,7 @@ export default function DeckCard({ slide={firstSlide} className="rounded-none" aspectRatio={deck.aspectRatio} + designSystem={designSystem} /> )}
diff --git a/templates/slides/app/components/deck/SlideRenderer.tsx b/templates/slides/app/components/deck/SlideRenderer.tsx index 4f6e61dab9..784eb51af0 100644 --- a/templates/slides/app/components/deck/SlideRenderer.tsx +++ b/templates/slides/app/components/deck/SlideRenderer.tsx @@ -13,6 +13,7 @@ import rehypeRaw from "rehype-raw"; import { Skeleton } from "@/components/ui/skeleton"; import type { Slide } from "@/context/DeckContext"; +import { DEFAULT_DESIGN_SYSTEM } from "@/hooks/use-deck-design-system"; import { type AspectRatio, getAspectRatioDims } from "@/lib/aspect-ratios"; import { sanitizeCssValue, @@ -545,25 +546,26 @@ export function SlideInner({ height: dims.height, }; - const bg = slide.background || "bg-[#000000]"; + const bg = slide.background || "var(--ds-bg, #000000)"; const isGradientClass = bg.startsWith("bg-"); const safeBackground = !isGradientClass ? sanitizeCssValue(bg) : null; const bgStyle = safeBackground ? { background: safeBackground } : undefined; const bgClass = isGradientClass ? bg : ""; const isCentered = slide.layout === "title"; - const dsStyle = designSystem - ? ({ - "--ds-accent": designSystem.colors.accent, - "--ds-bg": designSystem.colors.background, - "--ds-text": designSystem.colors.text, - "--ds-text-muted": designSystem.colors.textMuted, - "--ds-heading-font": designSystem.typography.headingFont, - "--ds-body-font": designSystem.typography.bodyFont, - "--ds-primary": designSystem.colors.primary, - "--ds-radius": designSystem.borders.radius, - } as React.CSSProperties) - : {}; + const ds = designSystem ?? DEFAULT_DESIGN_SYSTEM; + const dsStyle = { + "--ds-accent": ds.colors.accent, + "--ds-bg": ds.colors.background, + "--ds-text": ds.colors.text, + "--ds-text-muted": ds.colors.textMuted, + "--ds-primary": ds.colors.primary, + "--ds-heading-font": ds.typography.headingFont, + "--ds-body-font": ds.typography.bodyFont, + "--ds-heading-weight": String(ds.typography.headingWeight), + "--ds-body-weight": String(ds.typography.bodyWeight), + "--ds-radius": ds.borders.radius, + } as React.CSSProperties; // If slide has excalidraw data, render it as a static SVG thumbnail if ( diff --git a/templates/slides/app/components/editor/EditorSidebar.tsx b/templates/slides/app/components/editor/EditorSidebar.tsx index 25ff1accee..eb72185b1e 100644 --- a/templates/slides/app/components/editor/EditorSidebar.tsx +++ b/templates/slides/app/components/editor/EditorSidebar.tsx @@ -36,6 +36,8 @@ import { useAgentGenerating } from "@/hooks/use-agent-generating"; import { toast } from "@/hooks/use-toast"; import type { AspectRatio } from "@/lib/aspect-ratios"; +import type { DesignSystemData } from "../../../shared/api"; + interface EditorSidebarProps { slides: Slide[]; activeSlideId: string; @@ -49,6 +51,8 @@ interface EditorSidebarProps { slidePresence?: Map; /** Deck aspect ratio (defaults to 16:9 when omitted) */ aspectRatio?: AspectRatio; + /** Design system to forward to SlideRenderer for CSS custom properties */ + designSystem?: DesignSystemData; } const MAX_SOURCE_CONTEXT_CHARS = 60_000; @@ -161,6 +165,7 @@ function SortableSlideThumb({ registerButtonRef, presenceUsers = [], aspectRatio, + designSystem, }: { slide: Slide; index: number; @@ -171,6 +176,7 @@ function SortableSlideThumb({ registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void; presenceUsers?: CollabUser[]; aspectRatio?: AspectRatio; + designSystem?: DesignSystemData; }) { const t = useT(); const { @@ -242,7 +248,11 @@ function SortableSlideThumb({ : "rgba(255,255,255,0.06)", }} > - +
@@ -543,6 +553,7 @@ export default function EditorSidebar({ onAddEmptySlide, slidePresence, aspectRatio, + designSystem, }: EditorSidebarProps) { const t = useT(); const activeIndex = slides.findIndex((s) => s.id === activeSlideId); @@ -647,6 +658,7 @@ export default function EditorSidebar({ registerButtonRef={registerSlideButton} presenceUsers={slidePresence?.get(slide.id) ?? []} aspectRatio={aspectRatio} + designSystem={designSystem} /> ))} diff --git a/templates/slides/app/components/editor/HistoryPanel.tsx b/templates/slides/app/components/editor/HistoryPanel.tsx index f9dc425e20..d9a42465da 100644 --- a/templates/slides/app/components/editor/HistoryPanel.tsx +++ b/templates/slides/app/components/editor/HistoryPanel.tsx @@ -20,6 +20,7 @@ import { } from "@/components/ui/sheet"; import { Skeleton } from "@/components/ui/skeleton"; import type { Slide } from "@/context/DeckContext"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; import { useDeckVersion, useDeckVersions, @@ -85,6 +86,9 @@ export default function HistoryPanel({ const versions = versionsQuery.data?.versions ?? []; const selectedVersion = versionQuery.data; + const { designSystem: versionDesignSystem } = useDeckDesignSystem( + selectedVersion?.designSystemId ?? null, + ); const selectedSlides = useMemo( () => (selectedVersion?.slides ?? []).map((slide) => ({ @@ -194,6 +198,7 @@ export default function HistoryPanel({ | undefined } className="border border-border bg-black" + designSystem={versionDesignSystem} />

{t("history.slideNumber", { number: index + 1 })} diff --git a/templates/slides/app/components/presentation/PresentationView.tsx b/templates/slides/app/components/presentation/PresentationView.tsx index a53c71abc2..43033e3c2e 100644 --- a/templates/slides/app/components/presentation/PresentationView.tsx +++ b/templates/slides/app/components/presentation/PresentationView.tsx @@ -20,11 +20,14 @@ import { resolveSlideAnimationElement, } from "@/lib/slide-animation-elements"; +import type { DesignSystemData } from "../../../shared/api"; + interface PresentationViewProps { slides: Slide[]; deckId: string; startIndex?: number; aspectRatio?: AspectRatio; + designSystem?: DesignSystemData; } // ─── Element animation helpers ──────────────────────────────────────────────── @@ -153,6 +156,7 @@ export default function PresentationView({ deckId, startIndex = 0, aspectRatio, + designSystem, }: PresentationViewProps) { const t = useT(); const safeSlides = useMemo( @@ -448,6 +452,7 @@ export default function PresentationView({ slide={safeSlides[prevIndex]} thumbnail={false} aspectRatio={aspectRatio} + designSystem={designSystem} />

)} @@ -462,6 +467,7 @@ export default function PresentationView({ slide={displaySlide} thumbnail={false} aspectRatio={aspectRatio} + designSystem={designSystem} />
diff --git a/templates/slides/app/pages/DeckEditor.tsx b/templates/slides/app/pages/DeckEditor.tsx index 98085f766e..52da48764d 100644 --- a/templates/slides/app/pages/DeckEditor.tsx +++ b/templates/slides/app/pages/DeckEditor.tsx @@ -908,6 +908,7 @@ export default function DeckEditor() { }} slidePresence={slidePresence} aspectRatio={deck.aspectRatio} + designSystem={designSystem} />
diff --git a/templates/slides/app/pages/Index.tsx b/templates/slides/app/pages/Index.tsx index b0d396658b..6cfec5677a 100644 --- a/templates/slides/app/pages/Index.tsx +++ b/templates/slides/app/pages/Index.tsx @@ -39,6 +39,7 @@ import { import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useDecks } from "@/context/DeckContext"; import { useAgentGenerating } from "@/hooks/use-agent-generating"; +import { mergeDesignSystemData } from "@/hooks/use-deck-design-system"; import { useDesignSystems } from "@/hooks/use-design-systems"; import { toast } from "@/hooks/use-toast"; import { savePromptToComposerDraft } from "@/lib/composer-draft"; @@ -163,6 +164,19 @@ export default function Index() { () => new Map(designSystems.map((ds) => [ds.id, ds.title])), [designSystems], ); + const designSystemById = useMemo( + () => + new Map( + designSystems.flatMap((ds) => { + try { + return [[ds.id, mergeDesignSystemData(JSON.parse(ds.data))]]; + } catch { + return []; + } + }), + ), + [designSystems], + ); const deckFilter = searchParams.get("createdBy") === "me" ? "mine" : "all"; const visibleDecks = useMemo( () => @@ -569,6 +583,7 @@ export default function Index() { ? designSystemTitleById.get(deck.designSystemId) : null } + designSystem={designSystemById.get(deck.designSystemId ?? "")} /> ))} {visibleDecks.length === 0 && ( diff --git a/templates/slides/app/pages/Presentation.tsx b/templates/slides/app/pages/Presentation.tsx index 36c5419601..41669cd239 100644 --- a/templates/slides/app/pages/Presentation.tsx +++ b/templates/slides/app/pages/Presentation.tsx @@ -5,6 +5,7 @@ import { useParams, Navigate, useSearchParams } from "react-router"; import PresentationView from "@/components/presentation/PresentationView"; import { useDecks } from "@/context/DeckContext"; import type { Deck } from "@/context/DeckContext"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; export default function Presentation() { const { id } = useParams<{ id: string }>(); @@ -18,6 +19,8 @@ export default function Presentation() { const contextDeck = getDeck(id || ""); const deck = contextDeck ?? fallbackDeck; + const { designSystem } = useDeckDesignSystem(deck?.designSystemId); + useEffect(() => { if (!id || loading || contextDeck) { if (contextDeck) { @@ -64,6 +67,7 @@ export default function Presentation() { deckId={id} startIndex={startSlide} aspectRatio={deck.aspectRatio} + designSystem={designSystem} /> ); } diff --git a/templates/slides/app/pages/SharedPresentation.tsx b/templates/slides/app/pages/SharedPresentation.tsx index bd8d1cacc0..2f2674e32b 100644 --- a/templates/slides/app/pages/SharedPresentation.tsx +++ b/templates/slides/app/pages/SharedPresentation.tsx @@ -1,11 +1,15 @@ import { appBasePath, useT } from "@agent-native/core/client"; import type { SharedDeckResponse } from "@shared/api"; import { IconAlertCircle } from "@tabler/icons-react"; -import { useState, useEffect } from "react"; +import { useMemo, useState, useEffect } from "react"; import { useParams } from "react-router"; import PresentationView from "@/components/presentation/PresentationView"; import type { Slide } from "@/context/DeckContext"; +import { + DEFAULT_DESIGN_SYSTEM, + mergeDesignSystemData, +} from "@/hooks/use-deck-design-system"; interface SharedPresentationProps { initialDeck?: SharedDeckResponse | null; @@ -21,6 +25,14 @@ export default function SharedPresentation({ const [deck, setDeck] = useState(initialDeck); const [error, setError] = useState(initialError); const [loading, setLoading] = useState(!initialDeck && !initialError); + const designSystem = useMemo(() => { + if (!deck?.designSystemSnapshot) return DEFAULT_DESIGN_SYSTEM; + try { + return mergeDesignSystemData(JSON.parse(deck.designSystemSnapshot)); + } catch { + return DEFAULT_DESIGN_SYSTEM; + } + }, [deck?.designSystemSnapshot]); useEffect(() => { if (!token) return; @@ -87,6 +99,7 @@ export default function SharedPresentation({ slides={slides} deckId={`__shared__/${token}`} aspectRatio={deck.aspectRatio} + designSystem={designSystem} /> ); } diff --git a/templates/slides/app/routes/slide.tsx b/templates/slides/app/routes/slide.tsx index 0d1198563e..57a8d9f01c 100644 --- a/templates/slides/app/routes/slide.tsx +++ b/templates/slides/app/routes/slide.tsx @@ -10,6 +10,7 @@ import { useSearchParams } from "react-router"; import SlideRenderer from "@/components/deck/SlideRenderer"; import type { Slide } from "@/context/DeckContext"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; import messages from "@/i18n/en-US"; import type { AspectRatio } from "@/lib/aspect-ratios"; @@ -44,6 +45,9 @@ export default function SlideRoute() { ); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); + const [designSystemId, setDesignSystemId] = useState(null); + + const { designSystem } = useDeckDesignSystem(designSystemId); const slideNumber = slideNumberParam !== null ? parseInt(slideNumberParam, 10) : null; @@ -91,6 +95,7 @@ export default function SlideRoute() { const idx = Math.max(0, Math.min(slideIndex, slides.length - 1)); setSlide(slides[idx]); setAspectRatio(deck.aspectRatio); + setDesignSystemId(deck.designSystemId ?? null); setLoading(false); }) .catch((err: unknown) => { @@ -119,6 +124,7 @@ export default function SlideRoute() { slide={slide} thumbnail={false} aspectRatio={aspectRatio} + designSystem={designSystem} /> {inEmbed && ( diff --git a/templates/slides/server/db/schema.ts b/templates/slides/server/db/schema.ts index c3dc3cd95e..efdbf07210 100644 --- a/templates/slides/server/db/schema.ts +++ b/templates/slides/server/db/schema.ts @@ -54,6 +54,8 @@ export const deckShareLinks = table("deck_share_links", { title: text("title").notNull(), slides: text("slides").notNull(), // JSON array of slide snapshots aspectRatio: text("aspect_ratio"), + designSystemId: text("design_system_id"), + designSystemSnapshot: text("design_system_snapshot"), createdAt: text("created_at").notNull().default(now()), }); diff --git a/templates/slides/server/handlers/share.ts b/templates/slides/server/handlers/share.ts index 4917941515..4aa4fdecb1 100644 --- a/templates/slides/server/handlers/share.ts +++ b/templates/slides/server/handlers/share.ts @@ -53,11 +53,23 @@ async function createShareLink(event: any, deckId: string) { const db = getDb(); let storedDeck: any; let title = "Untitled"; + let designSystemId: string | null = null; + let designSystemSnapshot: string | null = null; try { const access = await assertAccess("deck", deckId, "admin"); title = access.resource.title ?? "Untitled"; storedDeck = JSON.parse(access.resource.data); + designSystemId = access.resource.designSystemId ?? null; + + if (access.resource.designSystemId) { + const [ds] = await db + .select({ data: schema.designSystems.data }) + .from(schema.designSystems) + .where(eq(schema.designSystems.id, access.resource.designSystemId)) + .limit(1); + designSystemSnapshot = ds?.data ?? null; + } } catch (err) { if (err instanceof ForbiddenError) { setResponseStatus(event, err.statusCode); @@ -83,6 +95,8 @@ async function createShareLink(event: any, deckId: string) { title: title || storedDeck.title || "Untitled", slides: JSON.stringify(slides), aspectRatio: storedDeck.aspectRatio ?? null, + designSystemId, + designSystemSnapshot, createdAt: now, }); @@ -135,6 +149,8 @@ export const getSharedDeck = defineEventHandler(async (event) => { title: shared.title, slides: JSON.parse(shared.slides), aspectRatio: shared.aspectRatio as SharedDeckResponse["aspectRatio"], + designSystemId: shared.designSystemId ?? null, + designSystemSnapshot: shared.designSystemSnapshot ?? null, }; return response; }); diff --git a/templates/slides/server/plugins/db.ts b/templates/slides/server/plugins/db.ts index e709ab8276..02da01dc18 100644 --- a/templates/slides/server/plugins/db.ts +++ b/templates/slides/server/plugins/db.ts @@ -177,6 +177,19 @@ export default runMigrations( CREATE INDEX IF NOT EXISTS slide_comments_deck_created_idx ON slide_comments (deck_id, created_at); CREATE INDEX IF NOT EXISTS slide_comments_deck_slide_created_idx ON slide_comments (deck_id, slide_id, created_at)`, }, + // v20: persist the deck's design system id on share-link snapshots so + // shared viewers resolve the same design system the deck creator chose. + { + version: 20, + sql: `ALTER TABLE deck_share_links ADD COLUMN IF NOT EXISTS design_system_id TEXT`, + }, + // v21: snapshot the resolved design system's raw `data` JSON at + // share-creation time so shared viewers read it directly instead of + // calling the auth-required get-design-system action anonymously. + { + version: 21, + sql: `ALTER TABLE deck_share_links ADD COLUMN IF NOT EXISTS design_system_snapshot TEXT`, + }, ], { table: "slides_migrations" }, ); diff --git a/templates/slides/shared/api.ts b/templates/slides/shared/api.ts index f9473d777b..1f76a2f8e8 100644 --- a/templates/slides/shared/api.ts +++ b/templates/slides/shared/api.ts @@ -75,6 +75,8 @@ export interface SharedDeckResponse { title: string; slides: SharedDeckSlide[]; aspectRatio?: import("./aspect-ratios").AspectRatio; + designSystemId?: string | null; + designSystemSnapshot?: string | null; } export type SharedSlideTransition =