Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 47 additions & 47 deletions templates/slides/.agents/skills/create-deck/SKILL.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions templates/slides/.agents/skills/design-systems/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<id> --designSystemId=<id>`
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:
Expand Down
22 changes: 10 additions & 12 deletions templates/slides/.agents/skills/slide-editing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | `&#x25CF;` 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 | `&#x25CF;` character (8px, white), gap: 20px, font-size: 22px, color: var(--ds-text) |
| Sub-bullets | `&#x25CB;` (open circle), padding-left: 36px |
| Bold terms | `<strong style="font-weight: 800; color: #fff;">Term</strong>` + description in rgba(255,255,255,0.55) |
| Accent color | `#00E5FF` (cyan) for section labels, emphasis, highlights |
| Bold terms | `<strong style="font-weight: var(--ds-heading-weight); color: var(--ds-text);">Term</strong>` + 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=<deckId>`
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=<deckId> --slideId=<slideId> --find="<old text>" --replace="<new text>"` for surgical token edits
2. Use `pnpm action update-slide --deckId=<deckId> --slideId=<slideId> --fullContent="<entire slide HTML>"` only for full slide rewrites

## Image Placeholders

Expand Down
1 change: 1 addition & 0 deletions templates/slides/actions/get-design-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default defineAction({
}),
readOnly: true,
http: { method: "GET" },
requiresAuth: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 requiresAuth false still leaves this action behind the global auth guard

This action is marked requiresAuth: false, but the Slides auth plugin still treats /_agent-native/actions/* as private unless the path is explicitly public. Anonymous requests to this route still 401 before the action handler runs, so shared viewers without a session cannot load design-system data.

Fix in Builder

run: async ({ id }) => {
const access = await resolveAccess("design-system", id);
if (!access) {
Expand Down
4 changes: 4 additions & 0 deletions templates/slides/app/components/deck/DeckCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -25,6 +26,7 @@ interface DeckCardProps {
onDuplicate: (id: string) => void;
isDuplicating?: boolean;
designSystemTitle?: string | null;
designSystem?: DesignSystemData;
}

export default function DeckCard({
Expand All @@ -34,6 +36,7 @@ export default function DeckCard({
onDuplicate,
isDuplicating = false,
designSystemTitle,
designSystem,
}: DeckCardProps) {
const firstSlide = deck.slides?.[0];
const [isRenaming, setIsRenaming] = useState(false);
Expand Down Expand Up @@ -93,6 +96,7 @@ export default function DeckCard({
slide={firstSlide}
className="rounded-none"
aspectRatio={deck.aspectRatio}
designSystem={designSystem}
/>
)}
<div className="absolute inset-0 bg-gradient-to-t from-[hsl(240,5%,8%)] via-transparent to-transparent opacity-60" />
Expand Down
28 changes: 15 additions & 13 deletions templates/slides/app/components/deck/SlideRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
Expand Down
13 changes: 12 additions & 1 deletion templates/slides/app/components/editor/EditorSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -47,6 +48,8 @@ interface EditorSidebarProps {
slidePresence?: Map<string, CollabUser[]>;
/** 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;
Expand Down Expand Up @@ -159,6 +162,7 @@ function SortableSlideThumb({
registerButtonRef,
presenceUsers = [],
aspectRatio,
designSystem,
}: {
slide: Slide;
index: number;
Expand All @@ -169,6 +173,7 @@ function SortableSlideThumb({
registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void;
presenceUsers?: CollabUser[];
aspectRatio?: AspectRatio;
designSystem?: DesignSystemData;
}) {
const {
attributes,
Expand Down Expand Up @@ -239,7 +244,11 @@ function SortableSlideThumb({
: "rgba(255,255,255,0.06)",
}}
>
<SlideRenderer slide={slide} aspectRatio={aspectRatio} />
<SlideRenderer
slide={slide}
aspectRatio={aspectRatio}
designSystem={designSystem}
/>
</div>
</div>
</button>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -638,6 +648,7 @@ export default function EditorSidebar({
registerButtonRef={registerSlideButton}
presenceUsers={slidePresence?.get(slide.id) ?? []}
aspectRatio={aspectRatio}
designSystem={designSystem}
/>
))}
</SortableContext>
Expand Down
5 changes: 5 additions & 0 deletions templates/slides/app/components/editor/HistoryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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) => ({
Expand Down Expand Up @@ -185,6 +189,7 @@ export default function HistoryPanel({
| undefined
}
className="border border-border bg-black"
designSystem={versionDesignSystem}
/>
<p className="mt-1.5 truncate text-[11px] text-muted-foreground">
Slide {index + 1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,6 +24,7 @@ interface PresentationViewProps {
deckId: string;
startIndex?: number;
aspectRatio?: AspectRatio;
designSystem?: DesignSystemData;
}

// ─── Element animation helpers ────────────────────────────────────────────────
Expand Down Expand Up @@ -151,6 +153,7 @@ export default function PresentationView({
deckId,
startIndex = 0,
aspectRatio,
designSystem,
}: PresentationViewProps) {
const safeSlides = useMemo(
() =>
Expand Down Expand Up @@ -445,6 +448,7 @@ export default function PresentationView({
slide={safeSlides[prevIndex]}
thumbnail={false}
aspectRatio={aspectRatio}
designSystem={designSystem}
/>
</div>
)}
Expand All @@ -459,6 +463,7 @@ export default function PresentationView({
slide={displaySlide}
thumbnail={false}
aspectRatio={aspectRatio}
designSystem={designSystem}
/>
</div>

Expand Down
1 change: 1 addition & 0 deletions templates/slides/app/pages/DeckEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,7 @@ export default function DeckEditor() {
}}
slidePresence={slidePresence}
aspectRatio={deck.aspectRatio}
designSystem={designSystem}
/>
</DndContext>
</div>
Expand Down
15 changes: 15 additions & 0 deletions templates/slides/app/pages/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
() =>
Expand Down Expand Up @@ -563,6 +577,7 @@ export default function Index() {
? designSystemTitleById.get(deck.designSystemId)
: null
}
designSystem={designSystemById.get(deck.designSystemId ?? "")}
/>
))}
{visibleDecks.length === 0 && (
Expand Down
4 changes: 4 additions & 0 deletions templates/slides/app/pages/Presentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>();
Expand All @@ -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) {
Expand Down Expand Up @@ -63,6 +66,7 @@ export default function Presentation() {
deckId={id}
startIndex={startSlide}
aspectRatio={deck.aspectRatio}
designSystem={designSystem}
/>
);
}
3 changes: 3 additions & 0 deletions templates/slides/app/pages/SharedPresentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +20,7 @@ export default function SharedPresentation({
const [deck, setDeck] = useState<SharedDeckResponse | null>(initialDeck);
const [error, setError] = useState(initialError);
const [loading, setLoading] = useState(!initialDeck && !initialError);
const { designSystem } = useDeckDesignSystem(deck?.designSystemId);

useEffect(() => {
if (!token) return;
Expand Down Expand Up @@ -85,6 +87,7 @@ export default function SharedPresentation({
slides={slides}
deckId={`__shared__/${token}`}
aspectRatio={deck.aspectRatio}
designSystem={designSystem}
/>
);
}
6 changes: 6 additions & 0 deletions templates/slides/app/routes/slide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" }];
Expand Down Expand Up @@ -39,6 +40,9 @@ export default function SlideRoute() {
);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [designSystemId, setDesignSystemId] = useState<string | null>(null);

const { designSystem } = useDeckDesignSystem(designSystemId);

const slideNumber =
slideNumberParam !== null ? parseInt(slideNumberParam, 10) : null;
Expand Down Expand Up @@ -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) => {
Expand All @@ -112,6 +117,7 @@ export default function SlideRoute() {
slide={slide}
thumbnail={false}
aspectRatio={aspectRatio}
designSystem={designSystem}
/>

{inEmbed && (
Expand Down
Loading
Loading