Skip to content

feat(bolt-slides): replace the slides starter with slides v2 BOU-2538 - #126

Draft
geotrev wants to merge 18 commits into
mainfrom
geotrev/bou-2538-add-slides-v2-to-starters-repo
Draft

feat(bolt-slides): replace the slides starter with slides v2 BOU-2538#126
geotrev wants to merge 18 commits into
mainfrom
geotrev/bou-2538-add-slides-v2-to-starters-repo

Conversation

@geotrev

@geotrev geotrev commented Aug 22, 2026

Copy link
Copy Markdown

Stable squash (2026-08-24): 0411df4b77467c6381efe1f59f85d13a0c47bbd1. Pair with bolt f1e9b32a9d (#7075), which pins this SHA.

Problem

The bolt-slides starter was JSX the agent rewrote on disk. Sharing and presenting also minted WebContainer URLs, which fail as visitor links and leak the preview origin.

Solution

Replace bolt-slides with a Pitch-style studio: decks are Postgres rows. The editor talks to deck-api; the agent writes deck / slides rows directly. Owner Present is in-place. The published origin is the audience deck (notes stripped). Share mints presenter-console and editor links on that origin. Host identity and published origin are injected by Bolt — stackblitz/bolt#7075.

Implementation Details

  • React 19 / Vite 8 / TS 6 / @/ kept. Skeleton chrome uses a Bolt DS token snapshot (BOU-2539) without depending on @blitz/design-system.
  • Three surfaces: editor (preview iframe /), audience present (published /, notes stripped; leftover /present?k= still works), presenter console (share or /present?presenter=1). Audience present has no presenter control.
  • Access order: share token → service-role bearer → X-Deck-Owner matching DECK_OWNER_SECRET → no token is present once the secret is set (local Vite with the secret unset stays owner). Opt-in via .bolt/config.json previewOwnerSecret. Owner proof is not stored in localStorage.
  • Share and P are disabled in Bolt preview until a public origin exists (no WebContainer URLs). After publish, Share mints presenter-console and editor links; P stays off in the iframe (BroadcastChannel is same-origin).
  • Skill authors table rows (not deck-api curls). Comments/profiles removed.

@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Decks live in Postgres. The editor uses deck-api; the agent writes
deck and slides rows. Owner Present is in-place. Share waits for a
published origin. BOU-2538.
@geotrev
geotrev force-pushed the geotrev/bou-2538-add-slides-v2-to-starters-repo branch from b44b0a3 to 0411df4 Compare August 24, 2026 13:24
Present-share ?presenter=1 no longer opens the speaker console. P popups
read the hash and close on Esc instead of becoming the editor or yanking
the live deck to slide 1.
Refresh now waits for debounce timers and in-flight PUTs, and drops a
stale /state snapshot if a write started after the GET. Share lookup
failure no longer defaults a ?k= tab to editor; deck patches coalesce.
Unlock rate-limit keys on a platform client IP (or the last X-Forwarded-For
hop), not the first. 500s no longer echo exception text. The migration
comment no longer teaches "no token = owner".
Notes now paste as plain text (matching EditableText) and wrap code
with escaped text. Freeform canvas drags abort on unmount and
pointercancel so a mid-drag slide change cannot keep writing.
Export is any reader (notes stripped unless edit); share minting is
owner-only. canEdit is documented as advisory — the API is the gate.
@geotrev

geotrev commented Aug 24, 2026

Copy link
Copy Markdown
Author

Review follow-ups (five commits on top of 0411df4)

These landed after a seam-first review of the v2 squash. They are grouped so later reviewers can treat each commit as a batch instead of re-deriving intent from the full studio diff.

Pinned in Bolt as 86fc0c50c9fb63619c99ba020d16b50adcf75999 (geotrev/bou-2538-pin-slides-v2).

1. Presenter is a capability, not a query string — 1d41faf

Merge blockers. ?presenter=1 used to open speaker chrome whenever it was in the URL.

  • isPresenter is now allowPresenter && the presenter query. Present-share (mode === 'present') stays audience-only even with ?presenter=1.
  • P from in-place Present opens a popup that reads #slide so it does not BroadcastChannel the live deck back to slide 1.
  • Esc / close in that popup closes the window. It does not tear down Present into the editor (onExit is omitted on the popup route).
  • Audience tabs subscribe to deck-sync only. Slide/click payloads are validated as integers.
  • Speaker chrome keys (S/G/F/D/H/O) are ignored in the presenter window.

2. Store write / refresh integrity — b670e57

  • refresh() waits for debounce timers and in-flight PUTs, and drops a /state snapshot if a write started after the GET. Focus/realtime refetch should no longer clobber notes or patchSlide.
  • shareInfo() failure on a ?k= / session token tab fail-closes to share-required. It no longer defaults mode to 'edit'.
  • updateDeck debounce PUTs the latest { title, transition, font, accent } instead of the values from the first keystroke.
  • Notes-only patchSlide is debounced; structural ops still bump a write generation so a concurrent refresh cannot win.

3. deck-api throttle and schema notes vs access()7f0d093

  • Unlock rate-limit key prefers cf-connecting-ip / x-real-ip, else the last X-Forwarded-For hop (first hop is client-controlled).
  • 500 bodies are { error: 'internal' }; exception text stays in logs.
  • Migration comment matches access(): once DECK_OWNER_SECRET is set, absence of a share token is not owner.

4. Editor hygiene — 88ac3ed

  • Notes paste is plain text (same idea as EditableText). code() HTML-escapes; highlight data-color already required a hex value.
  • Freeform canvas drag listeners abort on pointercancel and unmount so a mid-drag slide change cannot keep writing.

5. Docs — 86fc0c5

docs/cloud-setup.md matches the API: export is any reader (notes stripped unless edit); import is owner/edit; share minting is owner-only. canEdit is documented as advisory — the API is the gate.


Leftover edges (not bugs we treated as merge blockers)

Worth clicking if you are regression-testing this pin:

  • Same-tab /?presenter=1 with no opener: Esc/window.close() is a no-op. Exit by dropping ?presenter from the URL. This is the popup path; a blocked window.open stays on in-place Present.
  • Opening P still publishes clicks: 0 on first deck-sync. The slide should be correct; mid-build animations on the live window can snap back to the start of that slide.
  • In-place Present and the P popup are both channel leaders. Last write wins if they diverge.
  • Stale sessionStorage share token + failed /share lookup now shows the share gate instead of falling through to editor chrome. Clear origin storage to recover.
  • addSlide / duplicateSlide / deleteSlide / importDeck bump write generation but do not themselves flush a parked refresh. A collaborator change can wait until the next focus/visibility tick.
  • Formatted notes paste from Docs/Notion is intentionally plain text.

Out of scope here

Layout restyle (BOU-2539), a full test suite as a merge gate, live agent-SQL without a focus/visibility refetch, CORS *, fail-open when the owner secret is unset, unused Globe.tsx.

…ht set

add/duplicate/delete/import bumped writeGen but never tracked the POST, so a
focus or realtime refresh could apply a pre-write snapshot after the local set.
Port dark-theme text, border, background, and shadow onto editor and present controls without adding the design-system package, so slide content stays on deck tokens.
@geotrev
geotrev marked this pull request as draft August 25, 2026 13:29
Keep the canvas files and CSS for a later rework, but drop them from the registry, add-slide picker, and skill catalog so new decks cannot choose that layout.
Share and New slide share one focus trap so paging keys cannot fire while a modal is open, and the picker stays at max height. PDF/OG rasterization inlines iframe styles so accent, weight, and size survive the snapshot.
…ped text

Inlining every computed style and cloning into the parent produced empty black pages. Snapshot the iframe mount as before, and turn background-clip:text into a solid color so accent and figures still paint.
html-to-image and jsPDF have to run on the window thread. Painting after each progress update lets the busy toast show instead of freezing through the whole deck.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant