Skip to content

feat(bolt-slides): remove starter layouts; free-form responsive slides - #129

Open
inkko44 wants to merge 14 commits into
mainfrom
donald/bou-2910-remove-starter-slide-layouts-from-bolt-slides-free-form
Open

inkko44 wants to merge 14 commits into
mainfrom
donald/bou-2910-remove-starter-slide-layouts-from-bolt-slides-free-form

Conversation

@inkko44

@inkko44 inkko44 commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Closes BOU-2910

What

  • Removes the 30-layout starter catalog from bolt-slides: src/layouts/, src/components/, src/data/layoutProps.ts, and ~2,400 lines of layout-only CSS in base.css.
  • Slides are now free-form React components in src/slides/, referenced from deck.json by filename. A new src/slide/registry.tsx globs that folder; SlideView renders through it, so the rail, grid, Present, speaker view and PDF export keep working unchanged. A missing file renders a clear fallback slide instead of crashing.
  • deck.json ships a single seed slide (welcome.tsx) that the agent replaces.
  • SKILL.md, .bolt/prompt and README.md rewritten: shell description, the deck.json ↔ component contract, bootstrap, optional helpers (Slide, Reveal, Build, tokens, in-place text edit). The layout menu, entry conditions, arc and typography rules are gone. The one design rule left is every slide must be responsive.

Unchanged

Studio (side panel, grid, reorder / duplicate / delete, notes), dock, Present, speaker view, PDF / JSON download, deck.json persistence, theming tokens.

Verification

  • npm run typecheck, npm run lint, npm run build pass.
  • Headless Chrome renders of studio (with rail thumbnail), Present and speaker view at 1280×720 show the component slide correctly.
  • At 390px wide the headline overflows in Present; origin/main renders identically at that size, so it is pre-existing engine behavior.

Note

deck.json's layout field keeps its name (it now means the component filename) to avoid touching the store, export and studio paths that read it.

🤖 Generated with Claude Code

…s (BOU-2910)

Slides are now React components authored per deck in src/slides/ and
referenced from deck.json by filename (import.meta.glob registry in
src/slide/registry.tsx). The 30-layout catalog (src/layouts, src/components,
src/data/layoutProps.ts) and its ~2,400 lines of layout-only CSS are gone.

The studio, dock, rail, grid, notes, Present, speaker view, PDF/JSON
download and deck.json persistence are unchanged and render component
slides through the same SlideView path.

SKILL.md, .bolt/prompt and README now describe the deck.json <-> component
contract and the shell; the layout menu and styling rules are dropped.
The single design constraint is that every slide must be responsive.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@bolt-new-by-stackblitz

Copy link
Copy Markdown

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

inkko44 and others added 13 commits September 29, 2026 11:17
The first pass removed the layout catalog but left the look behind: the
dark/blue token theme, the spotlight + grain + vignette atmosphere on every
slide, the fluid type atoms (.kicker/.headline/.accent-text), the Inter
@import, applyFont defaulting to Inter, and a skill example + seed slide
that used all of it. Bolt copied that and produced the same blue-on-black
text decks.

- base.css: atmosphere and type atoms removed; only reset, slide box,
  .cols and the ==accent== marker remain.
- tokens.css: reduced to the variables the shell reads (--bg, --fg,
  --accent, fonts, gutters) with neutral defaults.
- applyFont imposes nothing when deck.font is unset.
- Fallback slides and the seed slide use plain inline styles.
- SKILL.md: no-house-style contract, a design section (palette, type,
  imagery, composition are the agent's to decide per deck), example code
  without atoms. README and .bolt/prompt updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n by default

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…creens

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tage-centered alignment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…; data-viz and legibility rules

SlideView measures its own box: up to 1600px wide the slide reflows; above
that the whole slide is scaled up proportionally, so ultrawide screens show
the 1600px composition larger instead of stretched thin. The scaled box is
a CSS container named 'slide', so cqw/cqh and @container queries measure
the stage (also fixing thumbnails and the PDF, which never matched vw).

Skill: design for 390->1600 and fill the stage at 1600; use cqw/cqh and
@container instead of vw/vh and media queries; a data/charts section
(the number is the hero, charts at presentation scale, labels never inside
data-sized shapes, no dashboard furniture); kicker above headline; a
legibility floor to check before finishing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…as a failure

SlideView measures every live slide after its entrance settles and logs a
console error naming the slide file and how far its content (text/media
elements only) leaves the stage. Bolt surfaces console errors to the agent,
so an overflowing slide becomes a failure to fix rather than advice to
follow. Thumbnails and export are not measured.

Skill: 'nothing out of view — ever', the error to look for, tactics in
order (cut copy, split the slide, use the width, shrink media before text),
and the shortest-stage budget; the legibility floor references the check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Width-only scaling shrank the inner stage's height whenever a window got
wider at a fixed height (2400x900 became a 1600x600 stage), pushing content
that fit at 900 tall out of view. The scale is now min(w/1600, h/900), so
the stage is never narrower than 1600 or shorter than 900; extra width is
room, not size. Stage constants and the fallback style move to
src/slide/stage.ts (Fast Refresh lint). Skill: height is the scarce axis —
tie vertical sizes to cqh, not cqw.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nnot push content out of view

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…not shrink

The shell never scales a slide down (upscale is clamped at 1), but the
skill's cqw-first sizing led the agent to shrink desktop compositions on
phones. SlideView now provides a StageCtx with the stage's width, height,
upscale and a narrow flag (< 700px); slide components read it with
useStage() from @/slide/stage to restack in JSX. Skill: below ~700px the
slide is a different one-column layout, clamp() minimums are real phone
reading sizes, and the narrow check must read like a phone page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… on a narrow stage

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
useStage() now also reports compact (< 1100px, iPad portrait/landscape and
small windows) and portrait (taller than wide). Skill: phone, tablet and
portrait tiers each get a layout; the canonical text-beside-chart example
stacks on narrow or portrait stages; the self-check covers 390x844,
820x1180, 1180x820 and 1600x900.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ames a shell change

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e user names a shell change"

This reverts commit fbb640d.

@geotrev geotrev left a comment

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.

Requesting changes for two regressions in the default experience. In speaker view, the seed slide's text is near-white on white. In the rail, grid and presenter thumbnails, slide text inherits line-height: 0 and collapses. Both happen because the deleted base type rules were never replaced on .slide-view. Separately, the new default accent is invisible in the dark notes and presenter chrome, and CI prettier is red.

Checked on 1bf6c8d8: tsc -b, eslint and vite build pass locally. I rendered studio, Present (3200×1800, 2900×1200, 390×844) and speaker view in headless Chromium. The missing-component fallback works, and Present upscaling and tiers behave as documented.

Not covered by an inline comment:

  • Dead layout-era code is left behind: serializeCodeRoot (.cw-code / .cw-body) in copy/richDom.ts:84–100, DeckFieldKind 'code' in deckPath.ts, the .slide::before reduced-motion rule (base.css:1382), and stale CountUp / chart comments in visualEditSync.ts.
  • Possibly intentional, please confirm: theme-color is now white while the studio stays dark, and html { color-scheme: dark } was removed, so native scrollbars in .note-wyg / .pres-stage are now light.
  • Not verified: PDF export with fonts loaded through a slide's own <style>@import. exporter.tsx:150–158 only scans <style> / <link> in the main document.

p {
margin: 0;
line-height: 1.5;

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.

Major: speaker view shows a blank current slide, and thumbnail text collapses.

This PR deletes the base h1–h3 / p rules (line-height, color: var(--fg)), and nothing on .slide-view replaces them. Free-form slides now inherit typography from whatever surface hosts them:

  • .noir-thumb-frame { line-height: 0 } (base.css:397) wraps the rail, the grid and both presenter previews. Wrapped text there renders with line-height: 0. In the rail, the seed welcome.tsx paragraph has offsetHeight: 0 and draws over the headline (checked in headless Chromium).
  • .pres { color: var(--ed-text) } is near-white, and the new default --bg is #fff. The presenter's "Current slide" box renders the seed h1 as rgb(249,246,249) on white, so the slide looks empty.

This contradicts SKILL.md:347 ("--fg (text color the slide box inherits)").

Fix: reset typography on the slide root, e.g. .slide-view { color: var(--fg); font-family: var(--font-body); line-height: normal; text-align: start; }. That also covers the thumbnail <button>s, which currently pass UA button font, size and colour into the slide.

--fg: #f2f5f9;
--fg-muted: #9aa3b2;
--fg-faint: #5f6a7b;
--accent: #111111;

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.

Major: the default accent #111111 is invisible in the dark chrome.

The notes editor and the presenter sit on --ed-bg (rgb(17,17,20)) and still use --accent / --primary:

  • .pres-progress-fill: measured rgb(17,17,17) on rgb(17,17,20)
  • ==accent== / {c:accent} text in notes (.accent-text, rich.tsx:29)
  • .note-list li::marker, the .note-quote border, and the colour-button underline (notes.tsx:587)

Every deck without an explicit accent loses these.

Fix: point the chrome at --ed-brand / --ui-accent, or keep a default accent with real chroma.

if (!m.count) return;

const sides = [
m.bottom > OUT_OF_VIEW_TOLERANCE && `${Math.round(m.bottom / upscale)}px below`,

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.

Major: CI is red. The prettier check fails on this file: lines 249–252 are too long, and there is a double blank line at 129–130.

Fix: run npm run format.

import Build from '@/deck/Build';
import type { SlideComponent } from '@/slide/registry';

const Opening: SlideComponent = ({ slide }) => (

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.

Major: the contract example fails lint. const Opening: SlideComponent = ({ slide }) => (…) never uses slide. Copied into src/slides/, it fails npm run lint with @typescript-eslint/no-unused-vars (verified). The agent is told to treat lint as a gate, so the canonical template should pass it.

Fix: use () => ( and mention that { slide } is available when needed.

useOutOfViewGuard(stageRef, slide, live, upscale);
const textScale =
slide.props?.scale === 'xl' ? 1.3 : slide.props?.scale === 'lg' ? 1.15 : 1;
const zoom = textScale * upscale;

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.

Minor: props.scale makes useStage() disagree with cqw / @container.

zoom = textScale * upscale sizes the container box at 100/zoom%. But useStageUpscale reports outer / upscale and ignores textScale.

Verified: with scale: "xl" on a 1280-wide stage, the container is 985px and @container slide (max-width: 1099px) matches. useStage() reports width: 1280, compact: false. CSS and JSX branches therefore pick different tiers, and the documented "never narrower than 1600" box becomes 1231×692.

Fix: derive StageSize from the container's size (outer / zoom), and keep upscale as a separate field.

]
.filter(Boolean)
.join(', ');
const w = Math.round(m.box.width / upscale);

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.

Minor: the out-of-view message reports wrong sizes in the studio.

The guard divides getBoundingClientRect() by upscale, but the studio canvas also CSS-scales the stage. Verified: a 1280×800 slide in the studio logs "79px below on a 984×615 stage". The real values are about 104px on 1280×800 (Present reports correctly). The agent uses this message to resize content.

Fix: compute the factor as box.width / stage.offsetWidth, and apply the same correction for textScale.

<SlideScope.Provider value={{ slideId: slide.id, slide }}>
<div
ref={stageRef}
className={'slide-view' + (slideHasImage(slide) ? ' has-image' : '')}

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.

Minor: has-image is now a dead class, so text on image backgrounds defaults to dark-on-dark.

The .slide-view.has-image … rules that lightened text were deleted. The shell still paints a dark scrim (dim of at least 0.4), and the default --fg is now #111.

Fix: drop the class and tell SKILL.md readers to set light text on image backgrounds, or add .slide-view.has-image { color: #fff }.

stack. Three-up grids become two-up.
- **Portrait** (taller than wide — any iPad held upright, every phone):
anything side by side stacks; height is plentiful, width is not.
Restack with `@container slide (max-width: 700px)` / `(max-width:

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.

Minor: the tier guidance disagrees with stage.ts.

  • @container (max-width: 700px) / (max-width: 1100px) are inclusive, while narrow / compact are < 700 / < 1100. (orientation: portrait) matches h ≥ w, while portrait is h > w. CSS and JSX disagree at exactly 700 or 1100 wide, and on square stages.
  • Line 66 lists iPad landscape 1180 under the < 1100 tier, but compact is false there.
  • Line 110 recommends a 900px fallback, which is not a tier, and the .cols utility it suggests uses a window @media query that line 75 calls "wrong here".
  • Line 49: the code gives a 2008×900 stage for 2900×1300, not 2013.

Fix: document max-width: 699.98px / 1099.98px, or change the hook to <=. Fix the examples to match.

`--primary` (the `==accent==` marker; deck-level `accent` overrides them),
`--font-head` / `--font-body` (deck-level `font` overrides them), and
`--gutter` / `--gutter-y` (what `<Slide>` pads with). Their defaults are
neutral on purpose. Set them to the deck's design, or ignore them and

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.

Minor: the docs contradict each other on src/styles/ and on sizing.

  • Lines 32–34 list src/styles/ as shell ("Leave it alone"). Line 351 says to set the tokens to the deck's design, and .bolt/prompt says "theme in src/styles/tokens.css".
  • Lines 130/201 recommend 5–8cqw headlines and 8–14cqw figures, while lines 135–138 say vertical sizes must not follow cqw. On a 3200×900 stage (upscale 1), 14cqw is 448px.

Fix: carve out tokens.css explicitly, and cap the cqw sizes, e.g. min(14cqw, 30cqh).

@@ -1,5 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');

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.

Minor: Inter is no longer loaded, but it is still referenced.

Removing this @import affects two places:

  • --font-ui: 'Inter', … (chrome-tokens.css) now falls back to system-ui across the studio chrome.
  • The inter pairing in fonts.ts has no import (it is the "bundled default"), so "font": "inter" silently renders the system font.

Also, nothing in slide content uses --font-head any more, only notes headings. A deck-level font therefore no longer changes slide headings, which contradicts SKILL.md:349.

Fix: load Inter for the chrome and pairing, or drop the inter pairing. Then document that slides opt in with var(--font-head).

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.

2 participants