Never run build or tests until I ask manually.
Internal viewer for Octify decks. Bring your own HTML. Each deck is either a Sanity document with a slides[] array, or a folder under slides/<slug>/ with meta.json and <n>.html files. The app renders each slide inside a sandboxed iframe at a fixed 1920x1080 canvas, scaled to fit the viewport. There is no in-app editor, no markdown, no theming, no presets.
The job of this app is narrow: list decks, render them as a slide deck (viewer + present mode), generate per-deck PDFs and OG images. Nothing else.
Both work simultaneously. The loader merges results, with Sanity winning on slug collisions.
Sanity (preferred for non-developers): run pnpm studio (starts Sanity Studio standalone), create a deck document, paste each slide's HTML, publish. The Sanity webhook hits /api/revalidate and the new deck is live in seconds with no deploy. Requires NEXT_PUBLIC_SANITY_PROJECT_ID env var; without it the app silently skips Sanity and serves filesystem decks only.
Filesystem (preferred for developers): drop a folder into slides/<slug>/ with a meta.json and one HTML file per slide. Commit, push, deploy. Same model the app shipped with originally; works without any external service.
Every slide HTML must:
- Be a complete
<!doctype html>document. - Render against a 1920x1080 canvas.
body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; }. - Inline its CSS. No external CSS, no Google Fonts, no external scripts, no fetches. System font stacks only.
- Have a
<title>tag.
Images and other assets are uploaded to Sanity directly and referenced by their cdn.sanity.io URL inside the slide HTML's <img> tags. The app does not proxy assets; it does not host any.
studio/sanity.config.ts, Sanity Studio configuration.studio/schemas/deck.ts, Sanity schema for a deck (metadata + slides[]).- src/lib/sanity.ts, Sanity client and cache-tag identifiers.
- src/lib/decks.ts, GROQ-backed loader (
listDecks,getDeck,readSlide). - src/components/SlideFrame.tsx, fixed-canvas iframe with CSS scaling.
- src/components/Viewer.tsx, main viewer (chrome + stage + thumb strip).
- src/components/Present.tsx, fullscreen present mode.
- src/app/page.tsx, index of decks.
- src/app/c/[slug]/page.tsx, viewer route.
- src/app/c/[slug]/slides/[file]/route.ts, slide HTML serving (file = stringified slide index).
- src/app/c/[slug]/opengraph-image.tsx, per-deck social card.
- src/app/api/revalidate/route.ts, Sanity webhook receiver.
- studio/sanity.cli.ts, CLI config for
pnpm studio.
- No in-app editor (use Sanity Studio).
- No theming, no presets, no palettes.
- No markdown, no IR, no block components.
- No customization UI of any kind.
- No "easier authoring" shortcuts that let slides skip the BYO HTML contract.
If something needs to be different per deck, it lives in the deck's HTML. Not in the app.
The app boots without any of these. Without them, only filesystem decks under slides/ are served.
NEXT_PUBLIC_SANITY_PROJECT_ID, the Sanity project ID. Setting this enables the Sanity loader path.NEXT_PUBLIC_SANITY_DATASET, defaults toproduction.NEXT_PUBLIC_SANITY_API_VERSION, ISO date, defaults to2024-10-01.SANITY_READ_TOKEN, required only if previewing unpublished drafts.SANITY_WEBHOOK_SECRET, required for the/api/revalidatewebhook to verify signatures. Configure the same value in the Sanity webhook settings.
See .env.example.
- Brainstorm before building. Don't auto-implement non-trivial changes.
- Never auto-commit, never auto-push. A prior "go ahead" does not carry over to git.
- Commit messages: one line, under 72 characters, imperative voice. No body. No
Co-Authored-By: Claude. - Never use an em dash anywhere (sentences, lists, headers, code comments). Use a comma, colon, or rephrase.
- For tasks that can be parallelized, run agents in parallel.