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 c071a7b1d7..908783bcd5 100644 --- a/templates/slides/.agents/skills/design-systems/SKILL.md +++ b/templates/slides/.agents/skills/design-systems/SKILL.md @@ -50,6 +50,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/actions/get-design-system.ts b/templates/slides/actions/get-design-system.ts index 257232286f..1fb22beb80 100644 --- a/templates/slides/actions/get-design-system.ts +++ b/templates/slides/actions/get-design-system.ts @@ -11,6 +11,7 @@ export default defineAction({ }), readOnly: true, http: { method: "GET" }, + requiresAuth: false, run: async ({ id }) => { const access = await resolveAccess("design-system", id); if (!access) { diff --git a/templates/slides/app/components/deck/DeckCard.tsx b/templates/slides/app/components/deck/DeckCard.tsx index 7e616b8bf4..2eae34e25d 100644 --- a/templates/slides/app/components/deck/DeckCard.tsx +++ b/templates/slides/app/components/deck/DeckCard.tsx @@ -9,6 +9,7 @@ import { import { useState, useRef, useEffect } from "react"; import type { Deck } from "@/context/DeckContext"; import SlideRenderer from "./SlideRenderer"; +import type { DesignSystemData } from "../../../shared/api"; import { VisibilityBadge } from "@agent-native/core/client"; import { DropdownMenu, @@ -25,6 +26,7 @@ interface DeckCardProps { onDuplicate: (id: string) => void; isDuplicating?: boolean; designSystemTitle?: string | null; + designSystem?: DesignSystemData; } export default function DeckCard({ @@ -34,6 +36,7 @@ export default function DeckCard({ onDuplicate, isDuplicating = false, designSystemTitle, + designSystem, }: DeckCardProps) { const firstSlide = deck.slides?.[0]; const [isRenaming, setIsRenaming] = useState(false); @@ -93,6 +96,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 8b28445535..fbc2825a6f 100644 --- a/templates/slides/app/components/deck/SlideRenderer.tsx +++ b/templates/slides/app/components/deck/SlideRenderer.tsx @@ -15,6 +15,7 @@ import { MermaidRenderer } from "./MermaidRenderer"; import { ExcalidrawThumbnail, parseExcalidrawData } from "./ExcalidrawSlide"; import type { DesignSystemData } from "../../../shared/api"; import { type AspectRatio, getAspectRatioDims } from "@/lib/aspect-ratios"; +import { DEFAULT_DESIGN_SYSTEM } from "@/hooks/use-deck-design-system"; import { sanitizeCssValue, sanitizeSlideHtml, @@ -541,25 +542,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 8ca9cc0bbf..a82cb9eb10 100644 --- a/templates/slides/app/components/editor/EditorSidebar.tsx +++ b/templates/slides/app/components/editor/EditorSidebar.tsx @@ -16,6 +16,7 @@ import { } from "@tabler/icons-react"; import type { Slide } from "@/context/DeckContext"; import type { AspectRatio } from "@/lib/aspect-ratios"; +import type { DesignSystemData } from "../../../shared/api"; import SlideRenderer from "@/components/deck/SlideRenderer"; import { useAgentGenerating } from "@/hooks/use-agent-generating"; import type { UploadedFile } from "@/components/editor/PromptDialog"; @@ -47,6 +48,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; @@ -159,6 +162,7 @@ function SortableSlideThumb({ registerButtonRef, presenceUsers = [], aspectRatio, + designSystem, }: { slide: Slide; index: number; @@ -169,6 +173,7 @@ function SortableSlideThumb({ registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void; presenceUsers?: CollabUser[]; aspectRatio?: AspectRatio; + designSystem?: DesignSystemData; }) { const { attributes, @@ -239,7 +244,11 @@ function SortableSlideThumb({ : "rgba(255,255,255,0.06)", }} > - +
@@ -535,6 +544,7 @@ export default function EditorSidebar({ onAddEmptySlide, slidePresence, aspectRatio, + designSystem, }: EditorSidebarProps) { const activeIndex = slides.findIndex((s) => s.id === activeSlideId); const [addOpen, setAddOpen] = useState(false); @@ -638,6 +648,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 c2fd86537a..1191856764 100644 --- a/templates/slides/app/components/editor/HistoryPanel.tsx +++ b/templates/slides/app/components/editor/HistoryPanel.tsx @@ -24,6 +24,7 @@ import { useDeckVersions, useRestoreDeckVersion, } from "@/hooks/use-deck-versions"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; import type { AspectRatio } from "@/lib/aspect-ratios"; import type { DeckVersionSummary } from "../../../shared/api"; @@ -76,6 +77,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) => ({ @@ -185,6 +189,7 @@ export default function HistoryPanel({ | undefined } className="border border-border bg-black" + designSystem={versionDesignSystem} />

Slide {index + 1} diff --git a/templates/slides/app/components/presentation/PresentationView.tsx b/templates/slides/app/components/presentation/PresentationView.tsx index 160de55a34..8606ea457c 100644 --- a/templates/slides/app/components/presentation/PresentationView.tsx +++ b/templates/slides/app/components/presentation/PresentationView.tsx @@ -13,6 +13,7 @@ import type { } from "@/context/DeckContext"; import SlideRenderer from "@/components/deck/SlideRenderer"; import type { AspectRatio } from "@/lib/aspect-ratios"; +import type { DesignSystemData } from "../../../shared/api"; import { findLegacyAnimationContainer, resolveSlideAnimationElement, @@ -23,6 +24,7 @@ interface PresentationViewProps { deckId: string; startIndex?: number; aspectRatio?: AspectRatio; + designSystem?: DesignSystemData; } // ─── Element animation helpers ──────────────────────────────────────────────── @@ -151,6 +153,7 @@ export default function PresentationView({ deckId, startIndex = 0, aspectRatio, + designSystem, }: PresentationViewProps) { const safeSlides = useMemo( () => @@ -445,6 +448,7 @@ export default function PresentationView({ slide={safeSlides[prevIndex]} thumbnail={false} aspectRatio={aspectRatio} + designSystem={designSystem} />

)} @@ -459,6 +463,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 5a701d6b2d..d0bdf8c462 100644 --- a/templates/slides/app/pages/DeckEditor.tsx +++ b/templates/slides/app/pages/DeckEditor.tsx @@ -897,6 +897,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 045decee05..7286bd1fe4 100644 --- a/templates/slides/app/pages/Index.tsx +++ b/templates/slides/app/pages/Index.tsx @@ -8,6 +8,7 @@ import PromptPopover from "@/components/editor/PromptDialog"; import type { UploadedFile } from "@/components/editor/PromptDialog"; import { useAgentGenerating } from "@/hooks/use-agent-generating"; import { useDesignSystems } from "@/hooks/use-design-systems"; +import { mergeDesignSystemData } from "@/hooks/use-deck-design-system"; import { savePromptToComposerDraft } from "@/lib/composer-draft"; import { useSetHeaderActions, @@ -161,6 +162,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( () => @@ -563,6 +577,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 985eacac60..20082ef87a 100644 --- a/templates/slides/app/pages/Presentation.tsx +++ b/templates/slides/app/pages/Presentation.tsx @@ -4,6 +4,7 @@ import { useDecks } from "@/context/DeckContext"; import type { Deck } from "@/context/DeckContext"; import PresentationView from "@/components/presentation/PresentationView"; import { callAction } from "@agent-native/core/client"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; export default function Presentation() { const { id } = useParams<{ id: string }>(); @@ -17,6 +18,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) { @@ -63,6 +66,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 e8e4fc684b..982a49ef4b 100644 --- a/templates/slides/app/pages/SharedPresentation.tsx +++ b/templates/slides/app/pages/SharedPresentation.tsx @@ -5,6 +5,7 @@ import type { SharedDeckResponse } from "@shared/api"; import type { Slide } from "@/context/DeckContext"; import PresentationView from "@/components/presentation/PresentationView"; import { appBasePath } from "@agent-native/core/client"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; interface SharedPresentationProps { initialDeck?: SharedDeckResponse | null; @@ -19,6 +20,7 @@ export default function SharedPresentation({ const [deck, setDeck] = useState(initialDeck); const [error, setError] = useState(initialError); const [loading, setLoading] = useState(!initialDeck && !initialError); + const { designSystem } = useDeckDesignSystem(deck?.designSystemId); useEffect(() => { if (!token) return; @@ -85,6 +87,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 3c9ea2c422..6c611f4f4f 100644 --- a/templates/slides/app/routes/slide.tsx +++ b/templates/slides/app/routes/slide.tsx @@ -9,6 +9,7 @@ import { import SlideRenderer from "@/components/deck/SlideRenderer"; import type { Slide } from "@/context/DeckContext"; import type { AspectRatio } from "@/lib/aspect-ratios"; +import { useDeckDesignSystem } from "@/hooks/use-deck-design-system"; export function meta() { return [{ title: "Slide Preview" }]; @@ -39,6 +40,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; @@ -86,6 +90,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) => { @@ -112,6 +117,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..4f189f0185 100644 --- a/templates/slides/server/db/schema.ts +++ b/templates/slides/server/db/schema.ts @@ -54,6 +54,7 @@ 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"), createdAt: text("created_at").notNull().default(now()), }); diff --git a/templates/slides/server/handlers/share.ts b/templates/slides/server/handlers/share.ts index de6a3c2cfb..49adc22ce0 100644 --- a/templates/slides/server/handlers/share.ts +++ b/templates/slides/server/handlers/share.ts @@ -51,11 +51,13 @@ async function createShareLink(event: any, deckId: string) { const db = getDb(); let storedDeck: any; let title = "Untitled"; + let designSystemId: 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; } catch (err) { if (err instanceof ForbiddenError) { setResponseStatus(event, err.statusCode); @@ -81,6 +83,7 @@ async function createShareLink(event: any, deckId: string) { title: title || storedDeck.title || "Untitled", slides: JSON.stringify(slides), aspectRatio: storedDeck.aspectRatio ?? null, + designSystemId, createdAt: now, }); @@ -133,6 +136,7 @@ export const getSharedDeck = defineEventHandler(async (event) => { title: shared.title, slides: JSON.parse(shared.slides), aspectRatio: shared.aspectRatio as SharedDeckResponse["aspectRatio"], + designSystemId: shared.designSystemId ?? null, }; return response; }); diff --git a/templates/slides/server/plugins/db.ts b/templates/slides/server/plugins/db.ts index e709ab8276..dde4511c80 100644 --- a/templates/slides/server/plugins/db.ts +++ b/templates/slides/server/plugins/db.ts @@ -177,6 +177,12 @@ 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`, + }, ], { table: "slides_migrations" }, ); diff --git a/templates/slides/shared/api.ts b/templates/slides/shared/api.ts index f9473d777b..e3503429e5 100644 --- a/templates/slides/shared/api.ts +++ b/templates/slides/shared/api.ts @@ -75,6 +75,7 @@ export interface SharedDeckResponse { title: string; slides: SharedDeckSlide[]; aspectRatio?: import("./aspect-ratios").AspectRatio; + designSystemId?: string | null; } export type SharedSlideTransition =