Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6d36edd
feat(bolt-slides): replace the slides template with slides v2
geotrev Aug 21, 2026
1b56b80
feat(bolt-slides): store decks in Postgres via the deck-api edge func…
geotrev Aug 21, 2026
8800358
fix(bolt-slides): stop React 19 from wiping in-place slide text edits
geotrev Aug 21, 2026
e2704b7
feat(bolt-slides): refresh the editor as soon as deck-api writes
geotrev Aug 21, 2026
c9d0fe0
feat(bolt-slides): present the deck in place instead of a new tab
geotrev Aug 22, 2026
e937de3
fix(bolt-slides): return to the editor when the presenter console closes
geotrev Aug 22, 2026
9c78687
feat(bolt-slides): gate deck-api on service role or preview owner proof
geotrev Aug 22, 2026
80e0c04
feat(bolt-slides): mint share links on the published bolt.host origin
geotrev Aug 22, 2026
b3706d2
feat(bolt-slides): stop requiring a workspace owner secret
geotrev Aug 22, 2026
c05a97a
fix(bolt-slides): hide presenter close when there is nowhere to go
geotrev Aug 22, 2026
1de964a
feat(bolt-slides): opt into host preview-owner secret sync
geotrev Aug 22, 2026
8246aa1
feat(bolt-slides): drop unused comments and profiles
geotrev Aug 22, 2026
5fbaba0
fix(bolt-slides): wait for a live owner inject before loading
geotrev Aug 22, 2026
970b612
fix(bolt-slides): disable presenter window when this page isn't the p…
geotrev Aug 22, 2026
55359a6
fix(bolt-slides): disable Share until a public origin exists
geotrev Aug 22, 2026
b84f75f
fix(bolt-slides): treat service-role on Authorization or apikey as owner
geotrev Aug 23, 2026
4c0e98f
fix(bolt-slides): never send an empty Bearer to deck-api
geotrev Aug 23, 2026
bf5a3b6
fix(bolt-slides): author decks as table rows, not deck-api curls
geotrev Aug 23, 2026
b44b0a3
fix(bolt-slides): stop the first-turn prompt from curling deck-api
geotrev Aug 23, 2026
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
3 changes: 3 additions & 0 deletions bolt-slides/.bolt/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"previewOwnerSecret": "DECK_OWNER_SECRET"
}
2 changes: 2 additions & 0 deletions bolt-slides/.bolt/prompt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Import project modules with the `@/` path alias, which maps to `src/` (e.g. `@/components/Foo` == `src/components/Foo`), instead of deep relative paths like `../../components/Foo`.

This template uses Vite 8. `resolve.tsconfigPaths: true` is a valid built-in Vite option. Preserve it for the `@/` alias; do not claim it is invalid or add the `vite-tsconfig-paths` plugin.

Decks are rows in `deck` and `slides`. Follow `.bolt/skills/slides/SKILL.md`. Before authoring: apply `supabase/migrations/create_deck_tables.sql` (writing the file does not create the tables) and deploy `supabase/functions/deck-api` as-is — the editor needs it. Do not call `deck-api` to author. Do not rewrite the app.
596 changes: 288 additions & 308 deletions bolt-slides/.bolt/skills/slides/SKILL.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions bolt-slides/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
.vite
*.local

.vscode/*
Expand All @@ -21,3 +22,5 @@ dist-ssr
*.sln
*.sw?
.env

public/og.png
79 changes: 52 additions & 27 deletions bolt-slides/README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,61 @@
# Bolt slides skill
# Bolt Slides

A Bolt skill that builds a premium, **responsive React presentation deck** — classic
paged slides you present one at a time, with a Slidev-style floating dock + thumbnail
rail, grid overview, click-builds, annotation, and presenter mode — but each slide is a
responsive web layout (no fixed canvas, no clipping) built from a rich component
library.
A Pitch-style slide studio for [Bolt](https://bolt.new): a deck **editor**, a
premium **presentation engine**, and a bundled **skill** so Bolt's AI can
prompt entire decks into existence — which you then refine by hand.

## The skill
Decks are structured data stored in **Postgres**. Prompted decks write
`deck` / `slides` rows. The visual editor reads and writes through the
`deck-api` edge function (share links, speaker notes), so both share one
store.

This repo **is** the running app. The authoring guide lives at
[`.bolt/skills/slides/SKILL.md`](./.bolt/skills/slides/SKILL.md); the app itself sits
at the repo root — a complete Vite + React deck: the paged engine + chrome
(`src/deck/`), fourteen slide layouts (`src/components/`: Cover, BigNumber,
Contrast, Chat, Globe, Bento, Split, StatGrid, Section, Quote, Pricing, Steps,
Agenda, Team) plus a dozen building blocks (Table, Comparison, Tabs, Accordion, Timeline,
CodeWindow, BrowserFrame, SpotlightCard, charts, CountUp, TiltCard, Marquee, …),
and the token-driven theme (`src/styles/`). The engine is left as-is; only the
`:root` token block and the slides in `src/App.tsx` are authored per deck.
## Quick start

## Add it in Bolt
```bash
npm install
npm run dev # editor at http://localhost:5173 — Present swaps this view
```

1. In Bolt's **Add skill from GitHub**, paste this repo's URL —
`https://github.com/inkko44/bolt-slides-skill`.
2. The `slides` skill auto-discovers at `.bolt/skills/slides.md`.
3. Tell Bolt to use the `slides` skill and build a deck about your topic/brand.
On first use in Bolt the agent must apply
`supabase/migrations/create_deck_tables.sql` and deploy
`supabase/functions/deck-api` (the instance is already provisioned; the
schema and function are not). After that, add slides in the editor or prompt
a deck with the `slides` skill.

## What's inside

- `/` — Editor: thumbnail rail, click-to-edit text, speaker notes. The
bottom bar pages the deck and holds Export PDF, Present, and Share
(one link per mode: presentation, presenter console, or editor; optional
password). Share is disabled in Bolt preview until the project is
published. Present replaces the editor in place (Esc or the dock close
control returns to editing). From local Vite or the published site, **P**
opens a presenter console in a second window. In an embedded preview that
control stays disabled (it would open the preview's own URL); use Share
after publishing.
- `/present` — Same presentation engine, used by share links on the
published origin.

## Run it locally
## The skill

`.bolt/skills/slides/SKILL.md` covers bootstrap (apply the shipped
migration, deploy `deck-api` as-is) and authoring `deck` / `slides` rows.
Do not rewrite the app or call `deck-api` to author.

## Architecture

```bash
npm install
npm run dev
```
src/data/ types + zustand store (optimistic writes via deck-api)
src/layouts/ layout registry
src/components/ section components (locked)
src/deck/ presentation engine
src/edit/ editor
src/styles/tokens.css theme: edit :root values only
supabase/functions/deck-api edge function — browser / share backend
supabase/migrations/ schema (apply; writing the file is not enough)
```

## Theming

`npm run dev` opens the deck at `/`. Re-theme everything by editing one `:root` block
in `src/styles/tokens.css`.
Everything visual derives from the `:root` tokens in `src/styles/tokens.css`.
`--accent` must stay a solid color.
83 changes: 83 additions & 0 deletions bolt-slides/docs/cloud-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Deck backend (Bolt Cloud / Supabase)

The starter ships the schema and the `deck-api` edge function. A Supabase
project is already provisioned for every Bolt project; the agent still has
to **apply the migration** and **deploy the function** before the editor
can load slides. That is Step 0 in `.bolt/skills/slides/SKILL.md` — not
optional, not "only when publishing".

The agent authors `deck` / `slides` rows in Postgres. The browser never
queries those tables: it talks only to `deck-api`.

## Contract

The editor, presenter, and share links talk to:

```
$VITE_SUPABASE_URL/functions/v1/deck-api
```

The gateway still wants `Authorization` and `apikey`. Who is calling
decides which Bearer:

- **Browser (Bolt preview)** — anon key + host-injected `X-Deck-Owner`.
Bolt derives a per-project preview owner token, injects it into the
iframe, and (because `.bolt/config.json` sets `previewOwnerSecret`)
copies the same value to the `DECK_OWNER_SECRET` edge-function secret.
It is not in workspace `.env` and never a `VITE_` var.
- **Share links / published `bolt.host`** — anon key + `X-Share-Token` (and
`X-Share-Grant` after a password unlock). The bare published URL is not
the editor.

Custom headers `X-Share-Token`, `X-Share-Grant`, and `X-Deck-Owner` must be
allowed in CORS (the shipped function already does this). `GET /health` is
the deploy probe: `200` and no deck data, no owner credentials.

```
GET /health
GET /state
PUT /deck
POST /slides
PUT /slides/:id
POST /slides/:id/duplicate
DELETE /slides/:id
PUT /order
GET /export POST /import
GET|PUT|DELETE /shares[/:mode]
GET /share?token=…
POST /share/unlock
```

## Tables

`deck` (single row), `slides`, `shares`,
`share_grants`, `unlock_attempts`.

**RLS is on with no policies.** The browser must never query these tables
directly: `shares` holds password hashes and `slides` holds speaker notes.
The function uses the service role. Do not add `anon` CRUD policies.

## Permissions the function enforces

- `Authorization` or `apikey` equals `SUPABASE_SERVICE_ROLE_KEY` — **owner**
- `X-Deck-Owner` matching `DECK_OWNER_SECRET` — **owner**. Bolt injects the
preview owner token into the iframe and syncs it to this secret when
`previewOwnerSecret` is set in `.bolt/config.json`. Top-level published
`bolt.host` does not get the header.
- no share token, no service role, no owner proof — **401** once the secret
is configured (legacy: owner if the secret is unset, e.g. local Vite)
- `edit` share link — everything
- `presenter` — read, plus writing `notes` on a slide
- `present` — read, with `notes` stripped from the response
- export / import / share management — owner or `edit` only

A request carrying a share token is judged **by that token**, even from the
owner's own browser.

## Authoring

The skill writes `deck` and `slides` rows. Slide `props` use the same JSON
shape as `.bolt/skills/slides/SKILL.md`. After a browser write, `deck-api`
broadcasts on the `deck` channel so other open editors re-fetch (no table
SELECT; RLS stays locked). Direct table writes do not emit that ping — the
preview refetches on focus.
17 changes: 11 additions & 6 deletions bolt-slides/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Always replace the title + favicon emoji to match the deck topic -->
<title>Replace — your deck title</title>
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎞️</text></svg>"
/>
<title>Slides</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 178 227'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M103.267 198.762C87.1743 198.762 71.3565 192.914 62.2897 180.315L59.0909 195.151L0 226.551L6.37813 195.151L49.3961 0H102.089L86.8603 68.7856C99.1455 55.3229 110.567 50.3381 125.208 50.3381C156.823 50.3381 177.901 71.121 177.901 109.174C177.901 147.227 153.605 198.742 103.247 198.742L103.267 198.762ZM123.461 120.321C123.461 138.474 110.587 152.231 93.9056 152.231C84.5445 152.231 76.0469 148.718 70.493 142.576L78.6963 106.564C84.8389 100.421 91.8646 96.9083 100.068 96.9083C112.647 96.9083 123.48 106.269 123.48 120.321H123.461Z' fill='%231688FC'/%3E%3C/svg%3E" />
<meta name="theme-color" content="#05070a" />
<!-- OpenGraph — og.png is generated from slide 1 -->
<meta property="og:title" content="Slides" />
<meta property="og:description" content="A deck built with Bolt Slides." />
<meta property="og:type" content="website" />
<meta property="og:image" content="/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="/og.png" />
</head>
<body>
<div id="root"></div>
Expand Down
Loading
Loading