Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
41b21a5
feat(bolt-slides): replace template with slides v2 prototype
geotrev Aug 19, 2026
89ff9fc
feat(bolt-slides): re-apply toolchain modernization from #121
geotrev Aug 19, 2026
da4099b
refactor(bolt-slides): use the @/ alias for internal imports
geotrev Aug 19, 2026
ecba49b
feat(bolt-slides): render published decks from a build-time snapshot
geotrev Aug 19, 2026
df32a88
docs(bolt-slides): correct publishing guidance and restore .bolt/prompt
geotrev Aug 19, 2026
34df6c8
fix(bolt-slides): build with plain vite build, as the deploy does
geotrev Aug 19, 2026
746f1b7
test(bolt-slides): cover the deck build and the editor
geotrev Aug 19, 2026
e570c69
style(bolt-slides): format with the repo's prettier config
geotrev Aug 19, 2026
e06e86a
fix(bolt-slides): show an imported deck without a manual reload
geotrev Aug 19, 2026
d0e3861
docs(bolt-slides): stop the agent conflating the draft with the database
geotrev Aug 19, 2026
e963818
feat(bolt-slides): seed a three-slide deck instead of an empty one
geotrev Aug 19, 2026
ba5e632
fix(bolt-slides): stop the dev server overwriting an imported deck
geotrev Aug 19, 2026
7186f6c
feat(bolt-slides): apply an authored draft without waiting for the im…
geotrev Aug 20, 2026
57952dc
fix(bolt-slides): stop an import breaking the editor it lands on
geotrev Aug 20, 2026
c7d04f2
fix(bolt-slides): let the browser keep the text being typed into a slide
geotrev Aug 20, 2026
c5d78db
fix(bolt-slides): present in the frame the deck was given
geotrev Aug 20, 2026
5461042
feat(bolt-slides): share the published deck, not the dev server
geotrev Aug 20, 2026
19e8463
refactor(bolt-slides): reset to the upstream prototype
geotrev Aug 20, 2026
8faaf7e
build(bolt-slides): re-modernize the toolchain
geotrev Aug 20, 2026
81bf12a
feat(bolt-slides): add the deck schema
geotrev Aug 20, 2026
1e4b514
feat(bolt-slides): add the deck function
geotrev Aug 20, 2026
d54f06f
feat(bolt-slides): keep the deck in the database, not in the project
geotrev Aug 21, 2026
9c3f8eb
feat(bolt-slides): hand out links to the published deck
geotrev Aug 21, 2026
f9b077e
fix(bolt-slides): stop React rewriting the field being typed into
geotrev Aug 21, 2026
504d440
docs(bolt-slides): tell the agent there is one place a deck lives
geotrev Aug 21, 2026
797fbfe
test(bolt-slides): test the deck where it actually runs
geotrev Aug 21, 2026
2b186de
docs(bolt-slides): describe the deck that exists now
geotrev Aug 21, 2026
416cdf2
fix(bolt-slides): tell the agent to write the deck's editing key
geotrev Aug 21, 2026
4e18e27
fix(bolt-slides): read the deck's credentials from the dev server
geotrev Aug 21, 2026
3622aaf
fix(bolt-slides): explain a keyless editor instead of presenting it
geotrev Aug 21, 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
8 changes: 6 additions & 2 deletions bolt-slides/.bolt/prompt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
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 project is a finished slide studio, not a starting point to build one. A deck request is a request for content: use the `slides` skill, which carries the deck format and the layout catalog, and author into the deck rather than writing components.

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.
The deck is rows in this project's Postgres database, reached through the `deck` Edge Function in `supabase/functions/`. That is the only copy. Never write slides to a file, never add a dev-server route that serves them, and if the project has no database yet, say that it needs one instead of improvising somewhere to put the deck.

The tables carry no row level security policies on purpose — the anon key ships inside the published deck, so anything `anon` may read is readable by everyone the deck is shared with, speaker notes included. All access goes through the function, which holds the service role. Adding a policy is not the fix for a permission problem here.

Import project modules with the `@/` path alias, which maps to `src/` (e.g. `@/data/store` == `src/data/store`), instead of deep relative paths like `../../data/store`.
629 changes: 322 additions & 307 deletions bolt-slides/.bolt/skills/slides/SKILL.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions bolt-slides/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# The deck lives in Postgres, so the app needs a project to talk to.
#
# In Bolt these first two are written into .env for you when the project gets a
# database — you do not fill them in by hand.
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=

# The deck's editing key, created by supabase/schema.sql and read out of the
# deck row after the schema is applied:
#
# select owner_key from deck;
#
# Deliberately not prefixed VITE_: it reaches the app only while the dev server
# is serving (see vite.config.ts), so a published deck cannot be edited by
# whoever opens it. Sharing the editor is what an `edit` share link is for.
DECK_OWNER_KEY=
26 changes: 5 additions & 21 deletions bolt-slides/.gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

.vscode/*
!.vscode/extensions.json
.idea
node_modules/
dist/
.vite/
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# credentials for the project's database, plus the deck's own editing key
.env
139 changes: 113 additions & 26 deletions bolt-slides/README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,123 @@
# Bolt slides skill
# Bolt Slides — starter

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 deck lives in the project's Postgres database, reached through one Edge
Function. There is no local copy and no file to keep in sync: the editor, the
presenter console and the deck you publish are all looking at the same rows.

## Quick start

The project needs a database before it has anywhere to keep a deck. In Bolt, ask
for one and the rest is done for you — the schema is applied and the `deck`
function deployed. Until then the app says so instead of pretending to be empty.

```bash
npm install
npm run dev # editor at http://localhost:5173 · present at /present
```

Working outside Bolt? Point `.env` at a Supabase project (see `.env.example`),
apply `supabase/schema.sql`, deploy `supabase/functions/deck`, and copy the
deck's `owner_key` into `.env` as `DECK_OWNER_KEY`.

## What's inside

| Route | What it is |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/` | Editor — thumbnail rail (always open; drag to reorder, right-click to duplicate/delete/insert), click any text on the slide to edit it, inspector for layout props · background (color/gradient/image) · animation · transition, per-slide status, speaker notes |
| `/present` | Presentation — floating dock, side panel (S) and grid overview (G), click-builds, presenter view with notes + timer (P), annotation mode (D — pen, highlighter, laser, shapes, eraser, undo/redo), fullscreen (F) |

- **Share** makes one link per mode: the presentation (read only, never the
speaker notes), the presenter console (read, plus writing notes), or the
editor (full access). Any link can carry a password. Links point at the
published deck, since the address the editor runs on opens for nobody else.
- **Export PDF** renders every slide at 1280×720 and downloads a PDF.
- Drop a 1200×630 `public/og.png` in to give a shared link a preview card; the
OpenGraph tags in `index.html` are already wired to it.

## Who can do what

The rules are enforced in the `deck` function, because that is the only place
they can be: the anon key ships inside every published deck, so anyone the deck
is shared with can call the API with whatever headers they like.

- **The tables have row level security enabled and no policies at all.** That is
the model, not an omission — the anon key reaches nothing directly. Adding a
policy for `anon` would hand the audience the speaker notes and a way to
rewrite the deck.
- **Editing is proved by the deck's `owner_key`**, which `vite.config.ts` hands
to the app only while the dev server is serving it. `vite build` never defines
it, so a published deck is keyless by construction: whoever opens it cannot
edit it. Sharing the editor is what an `edit` link is for.
- **Passwords** are PBKDF2-SHA256 (210,000 iterations, per-link salt), and
attempts are capped per address so guessing a short password stays hopeless.
- **Speaker notes** are never sent to the audience view. Not hidden in the UI —
not sent.

## Decks as data

A slide is a row: `layout` + JSON `props` + background/animation/transition +
status/notes. 31 premium layouts (cover, section, statement, manifesto, big
number, quote, agenda, steps, pillars, timeline, contrast, comparison, table,
tabs, accordion, q&a, pricing, team, logos, poster, story, speaker, persona,
bento, stat grid, figures, chart, insight, chat, code) render those rows through
`src/layouts/` — so the AI can author decks and the editor can edit them without
either touching React code.

The whole deck moves as one JSON document, in and out:

```sql
select export_deck(); -- the deck as portable JSON
select import_deck($json$ { … } $json$); -- replace it, reporting what landed
```

Importing keeps slide identity — by `id` where the incoming deck names one,
otherwise by position — so re-importing an edited deck is a change rather than a
replacement. An open editor notices within a few seconds, because it polls the
deck's version rather than waiting to be reloaded.

## The skill

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.
`.bolt/skills/slides/SKILL.md` teaches Bolt's AI to author decks as JSON —
including which animation, transition, background and status to set per slide —
and to import them with `import_deck`. Ask Bolt for "a 12-slide seed pitch for …"
and refine what lands in the editor.

## Add it in Bolt
## Publishing

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.
Publish the project and the deck comes with it, live: the published site reads
the same database, so an edit made in the editor shows up on it. That is also
what makes a share link worth sending — it opens the deck as it is, not a copy
of it as it was.

## Run it locally
The first load of the published site records its own address (browsers set
`Origin`, and page scripts cannot forge it), which is the base every share link
is built on. A custom domain works without being configured anywhere; you can
also set it by hand in the Share dialog.

## Architecture

```bash
npm install
npm run dev
```
supabase/schema.sql ← the deck: tables, import_deck/export_deck, RLS with no policies
supabase/functions/deck/ ← the only way in: routing (routes.ts) + the rules (access.ts)
src/data/ ← types, the backend client, and the zustand store (optimistic writes)
src/layouts/ ← the layout registry: props schema + renderer per layout
src/components/ ← the premium section components (locked design system)
src/deck/ ← the presentation engine (dock, rail, builds, presenter)
src/slide/SlideView.tsx ← one slide row → pixels (backgrounds, animation modes)
src/edit/ ← the editor (rail, canvas, inspector, notes)
src/export/ ← PDF rendering (off-screen iframes)
src/styles/tokens.css ← the theme: edit :root values only; accent is ONE solid color
```

[docs/architecture.md](docs/architecture.md) is the long version: the route
contract, the permission rules, and why they sit where they do.

## 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` —
change the values (never the names) to re-brand the deck AND the editor chrome in
one place. `--accent` must stay a solid color.
Loading