Vibecode Beautiful.ai
track this build5 phases, 15 steps, beginner friendly0%You can build a template deck generator, but auto-layout slide behavior, template polish, brand controls, PowerPoint import/export, and collaboration are real work.
You are building a lean indie version of Beautiful.ai. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Beautiful.ai · indie build Slides defined as structured content bound to layout rules that compute positions, text that auto-fits within readable bounds, a theme applied by reference so a brand change updates every slide, and export to PDF and to a presentation format that opens correctly elsewhere. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite | the layout engine is the product and it is a pure function you want to test | | Layout | Rules computing positions from content, never free positioning | this is the entire premise; free positioning is a different product | | Rendering | SVG for the editor and for PDF, through one layout function | one geometry source so the preview and the export cannot disagree | | Export | PDF from the same layout, plus a presentation format for people who need the file | PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both | | Text | Measured with the real font before layout | auto-fit against a fallback font produces text that overflows once the real face loads | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build and the export pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real presentation application to test export** · varies; a free alternative is adequate for a fidelity check - Why: Phase 5 is only meaningful if you open the exported file where your audience will. Your own renderer agreeing with itself proves nothing. - Get it: Whichever your colleagues use. If several, test in each, because they differ. - Verify: An exported deck opens with text and images in the right places - [ ] **Fonts you are licensed to embed** · free with open licences - Why: Both exports embed or substitute fonts, and substitution is what breaks a deck on someone else's machine. - Get it: Openly licensed families with their licence files kept alongside. - Verify: Each bundled font permits embedding - [ ] **A real deck you have given before** · free - Why: Layout rules are only testable against real content, which has the long headings and awkward bullet counts that invented content does not. - Get it: Take a deck you actually presented and rebuild it. The slides that resist are where the layout rules need work. - Verify: A real deck of at least ten slides is rebuilt - [ ] **Test images including a tall portrait** · free - Why: Phase 4's focal point handling cannot be judged without an image whose subject is not centred. - Get it: Any photo library. One tall portrait with a face near the top is the case that matters. - Verify: The portrait is available as a fixture ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. - smart templates - brand controls - shared libraries - PowerPoint compatibility - collaboration - viewer analytics If one of those is essential to you, that is the reason to keep paying for Beautiful.ai, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Beautiful.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me slides generated from structured content, where the layout adapts instead of you nudging boxes to replace Beautiful.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The product here is that layout is computed rather than drawn. A slide is content plus a layout rule, and adding a bullet or a longer heading reflows the slide instead of overflowing it. If you find yourself building free positioning with snapping, you have built a different and much larger product. The second constraint is export. A deck that looks right in your renderer and wrong in PowerPoint is a deck nobody can present, because someone will always need the file. Test export fidelity from the first template rather than after building twenty. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite · the layout engine is the product and it is a pure function you want to test - Layout: Rules computing positions from content, never free positioning · this is the entire premise; free positioning is a different product - Rendering: SVG for the editor and for PDF, through one layout function · one geometry source so the preview and the export cannot disagree - Export: PDF from the same layout, plus a presentation format for people who need the file · PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both - Text: Measured with the real font before layout · auto-fit against a fallback font produces text that overflows once the real face loads ### Data model (create this before Phase 1) - decks(id, title, theme_id, created_at, updated_at) - slides(id, deck_id, ordinal, layout_key, content_json, notes) - layouts(key, name, slots_json, rules_json) · slots describe what content a layout accepts and rules compute geometry from it - themes(id, name, palette_json, fonts_json, spacing_json) - assets(id, deck_id, kind, path, bytes, focal_x, focal_y) - Content is stored as structure rather than as positioned boxes. Position is always derived, which is what makes reflow possible. ### Phase 1 · One layout that computes itself Build: Define one layout as data: the slots it accepts and the rules that turn content into geometry. Render it. Add a bullet and watch the slide rebalance. Write the layout function as pure, so it is testable without a browser. Trap: Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. Done when: Adding a fifth bullet to a four bullet slide rebalances spacing without overflow. No slide stores a position, verified by inspecting the schema. The layout function is pure and tested without a browser. A slot given more content than the layout can hold reports that rather than overflowing. Do not build yet: No themes, text fitting, images or export. ### Phase 2 · Text that fits and stays readable Build: Measure text with the real font loaded. Binary search the size until wrapped text fits its slot, with a floor below which the layout reports that the content is too long instead of shrinking further. Trap: Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: A long heading shrinks to fit and stops at the readable floor, reporting that the content is too long rather than continuing. Measurement waits for the real font. The same content produces the same size on a machine that does not have the font cached, verified by a cold load. Wrapping matches between preview and export. Do not build yet: No themes or images yet. ### Phase 3 · Themes applied by reference Build: A theme of palette, fonts and spacing. Layouts reference theme values rather than literal colours or sizes. Changing the theme reflows and recolours the whole deck without touching slide content. Trap: Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. Done when: Changing a theme colour updates every slide using it. Changing the theme font reflows every slide with correct auto-fit. No slide contains a literal colour or font size, verified by inspecting stored content. Switching themes on a finished deck produces a correct deck rather than broken layouts. Do not build yet: No images or export yet. ### Phase 4 · Images without ruining the layout Build: Image slots with a fit mode and a focal point. Crop to the slot's computed shape using the focal point. Downscale on import. The layout adapts to the image's presence rather than the image being placed on top of it. Trap: Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. Done when: A portrait image in a wide slot keeps its focal point visible. Changing the focal point updates the crop. An oversized image is downscaled on import. Adding an image changes the layout of the text around it rather than covering it. Do not build yet: No export yet. ### Phase 5 · Export, including the file people ask for Build: Export PDF from the same layout function as the preview. Export a presentation format by mapping computed geometry to its shapes and text boxes. Open the result in a real presentation application and record what does not survive. Trap: A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. Done when: A PDF matches the preview exactly for a deck with auto-fit text and a cropped image. The presentation export opens in a real presentation application with text and images in the right places. A written note records what does not survive the presentation export. Exporting twice produces identical files. Do not build yet: Generation from content comes last. ### Phase 6 · Generating a first draft from content Build: Take structured content, an outline or a document, and propose slides by choosing layouts and distributing content into slots. Every generated slide is a normal slide afterwards. Nothing is generated that the layout rules cannot render. Trap: Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. Done when: An outline produces a deck where every slide renders without overflow. Generated slides are editable exactly like hand-made ones. Content exceeding a slot's limit is split across slides rather than overflowing. Regenerating does not overwrite slides that have been edited. Do not build yet: Nothing after this. ### Out of scope (and why) - The smart template library, which is the product's actual value and is accumulated design work rather than code. - Brand controls and shared libraries across an organisation, which is the reason teams buy it. - Real-time collaborative editing. - Animation and transitions, which are mostly lost in export anyway and are a different kind of work. ### README must contain - That layout is computed and no position is ever stored - That auto-fit measures with the real font and stops at a readable floor - That themes are referenced and slides contain no literal colours or sizes - That the presentation format export is lossy, with the recorded list of what does not survive - That generation is constrained to what the layouts can render ===== AGENTS.md ===== # Agent instructions · Beautiful.ai indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, Rules computing positions from content, never free positioning, SVG for the editor and for PDF, through one layout function, PDF from the same layout, plus a presentation format for people who need the file, Measured with the real font before layout. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ===== BUILD_PLAN.md ===== # Build plan · Beautiful.ai Slides defined as structured content bound to layout rules that compute positions, text that auto-fits within readable bounds, a theme applied by reference so a brand change updates every slide, and export to PDF and to a presentation format that opens correctly elsewhere. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One layout that computes itself A slide reflows when its content changes, without anyone moving anything. ### Steps 1. Define layouts as data Slots and rules. Written down before implementation, like any format that others will extend. Files: `docs/layout-format.md` 2. Write the layout function pure Content and theme in, geometry out. No document object model access, so it is testable directly. Files: `src/layout.ts` 3. Never store positions Grep the schema. A stored position is the thing that breaks reflow. Files: `src/model.ts` ### Done when - [ ] Adding a bullet rebalances rather than overflowing - [ ] No position is stored anywhere - [ ] The layout function is tested without a browser - [ ] Excess content is reported rather than overflowing ### Watch out - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. ## Phase 2 · Text that fits and stays readable Auto-fit shrinks text to fit and stops before it becomes unreadable. ### Steps 1. Load fonts before measuring Await font loading, then measure. Re-measure on load completion if you measured early. Files: `src/text.ts` 2. Binary search to fit with a floor Below the floor is a content problem, not a sizing problem, and the layout should say so. Files: `src/autofit.ts` 3. Verify on a cold load A profile without the font cached. This is where a fallback measurement shows up. ### Done when - [ ] Auto-fit stops at a readable floor and reports overflow - [ ] Measurement waits for the real font - [ ] A cold load produces the same sizes - [ ] Preview and export wrap identically ### Watch out - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. ## Phase 3 · Themes applied by reference Changing the brand updates every slide at once. ### Steps 1. Model themes as referenced values Palette, fonts, spacing. Layouts read from the theme rather than embedding values. Files: `src/theme.ts` 2. Reflow on theme change Because font metrics changed, auto-fit must run again across the deck. Files: `src/deck.ts` 3. Forbid literal overrides Or make them named variants. This is the rule that keeps the premise intact. Files: `src/model.ts` ### Done when - [ ] A theme colour change updates every slide - [ ] A theme font change reflows the deck correctly - [ ] No literal colours or sizes are stored in slides - [ ] Switching themes on a finished deck works ### Watch out - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. ## Phase 4 · Images without ruining the layout An image of any shape fits its slot without distortion or a decapitated subject. ### Steps 1. Crop with a focal point Cover fit anchored to the focal point, adjustable, defaulting to centre. Files: `src/image.ts` 2. Downscale on import A twelve megapixel photo in a slide is wasted bytes and a slow editor. Files: `src/assets.ts` 3. Let images affect layout The layout rule accounts for the image slot. An image placed over text is the thing this product avoids. Files: `src/layout.ts` ### Done when - [ ] A portrait in a wide slot keeps its focal point - [ ] Changing the focal point updates the crop - [ ] Oversized images are downscaled on import - [ ] Adding an image reflows surrounding text ### Watch out - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. ## Phase 5 · Export, including the file people ask for A PDF is faithful, and the presentation file opens correctly enough to present from. ### Steps 1. Export PDF from the layout function The same geometry as the preview. Any divergence here is a bug rather than a format difference. Files: `src/export-pdf.ts` 2. Map geometry to the presentation format Absolute boxes with embedded or substituted fonts, accepting the loss. Files: `src/export-pptx.ts` 3. Open it in a real application and record the losses From the first template. This is the check people skip and regret. Files: `docs/export-fidelity.md` ### Done when - [ ] PDF matches the preview exactly - [ ] The presentation export opens correctly in a real application - [ ] The fidelity note records what is lost - [ ] Two exports are identical ### Watch out - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ## Not in this build - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. ## After v1, if you want it - A speaker view with notes and a timer, which needs no new layout work - Chart slots fed from a table, laid out by the same rules - A second theme built from a real brand guideline, which tests whether the theme model is general ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. VITE_AUTOFIT_MIN_PT=14 # Required. Longest edge kept on import. VITE_MAX_IMAGE_PX=3000 # Required. Self-hosted fonts with licences beside them. VITE_FONT_DIR=/fonts # Required. Rasterisation density for images in exports. VITE_EXPORT_DPI=150 # Required. Layout definitions as data. Adding a layout is a file here. VITE_LAYOUT_DIR=/layouts
You are building a lean indie version of Beautiful.ai. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Beautiful.ai · indie build Slides defined as structured content bound to layout rules that compute positions, text that auto-fits within readable bounds, a theme applied by reference so a brand change updates every slide, and export to PDF and to a presentation format that opens correctly elsewhere. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite | the layout engine is the product and it is a pure function you want to test | | Layout | Rules computing positions from content, never free positioning | this is the entire premise; free positioning is a different product | | Rendering | SVG for the editor and for PDF, through one layout function | one geometry source so the preview and the export cannot disagree | | Export | PDF from the same layout, plus a presentation format for people who need the file | PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both | | Text | Measured with the real font before layout | auto-fit against a fallback font produces text that overflows once the real face loads | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build and the export pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real presentation application to test export** · varies; a free alternative is adequate for a fidelity check - Why: Phase 5 is only meaningful if you open the exported file where your audience will. Your own renderer agreeing with itself proves nothing. - Get it: Whichever your colleagues use. If several, test in each, because they differ. - Verify: An exported deck opens with text and images in the right places - [ ] **Fonts you are licensed to embed** · free with open licences - Why: Both exports embed or substitute fonts, and substitution is what breaks a deck on someone else's machine. - Get it: Openly licensed families with their licence files kept alongside. - Verify: Each bundled font permits embedding - [ ] **A real deck you have given before** · free - Why: Layout rules are only testable against real content, which has the long headings and awkward bullet counts that invented content does not. - Get it: Take a deck you actually presented and rebuild it. The slides that resist are where the layout rules need work. - Verify: A real deck of at least ten slides is rebuilt - [ ] **Test images including a tall portrait** · free - Why: Phase 4's focal point handling cannot be judged without an image whose subject is not centred. - Get it: Any photo library. One tall portrait with a face near the top is the case that matters. - Verify: The portrait is available as a fixture ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. - smart templates - brand controls - shared libraries - PowerPoint compatibility - collaboration - viewer analytics If one of those is essential to you, that is the reason to keep paying for Beautiful.ai, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Beautiful.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me slides generated from structured content, where the layout adapts instead of you nudging boxes to replace Beautiful.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The product here is that layout is computed rather than drawn. A slide is content plus a layout rule, and adding a bullet or a longer heading reflows the slide instead of overflowing it. If you find yourself building free positioning with snapping, you have built a different and much larger product. The second constraint is export. A deck that looks right in your renderer and wrong in PowerPoint is a deck nobody can present, because someone will always need the file. Test export fidelity from the first template rather than after building twenty. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite · the layout engine is the product and it is a pure function you want to test - Layout: Rules computing positions from content, never free positioning · this is the entire premise; free positioning is a different product - Rendering: SVG for the editor and for PDF, through one layout function · one geometry source so the preview and the export cannot disagree - Export: PDF from the same layout, plus a presentation format for people who need the file · PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both - Text: Measured with the real font before layout · auto-fit against a fallback font produces text that overflows once the real face loads ### Data model (create this before Phase 1) - decks(id, title, theme_id, created_at, updated_at) - slides(id, deck_id, ordinal, layout_key, content_json, notes) - layouts(key, name, slots_json, rules_json) · slots describe what content a layout accepts and rules compute geometry from it - themes(id, name, palette_json, fonts_json, spacing_json) - assets(id, deck_id, kind, path, bytes, focal_x, focal_y) - Content is stored as structure rather than as positioned boxes. Position is always derived, which is what makes reflow possible. ### Phase 1 · One layout that computes itself Build: Define one layout as data: the slots it accepts and the rules that turn content into geometry. Render it. Add a bullet and watch the slide rebalance. Write the layout function as pure, so it is testable without a browser. Trap: Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. Done when: Adding a fifth bullet to a four bullet slide rebalances spacing without overflow. No slide stores a position, verified by inspecting the schema. The layout function is pure and tested without a browser. A slot given more content than the layout can hold reports that rather than overflowing. Do not build yet: No themes, text fitting, images or export. ### Phase 2 · Text that fits and stays readable Build: Measure text with the real font loaded. Binary search the size until wrapped text fits its slot, with a floor below which the layout reports that the content is too long instead of shrinking further. Trap: Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: A long heading shrinks to fit and stops at the readable floor, reporting that the content is too long rather than continuing. Measurement waits for the real font. The same content produces the same size on a machine that does not have the font cached, verified by a cold load. Wrapping matches between preview and export. Do not build yet: No themes or images yet. ### Phase 3 · Themes applied by reference Build: A theme of palette, fonts and spacing. Layouts reference theme values rather than literal colours or sizes. Changing the theme reflows and recolours the whole deck without touching slide content. Trap: Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. Done when: Changing a theme colour updates every slide using it. Changing the theme font reflows every slide with correct auto-fit. No slide contains a literal colour or font size, verified by inspecting stored content. Switching themes on a finished deck produces a correct deck rather than broken layouts. Do not build yet: No images or export yet. ### Phase 4 · Images without ruining the layout Build: Image slots with a fit mode and a focal point. Crop to the slot's computed shape using the focal point. Downscale on import. The layout adapts to the image's presence rather than the image being placed on top of it. Trap: Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. Done when: A portrait image in a wide slot keeps its focal point visible. Changing the focal point updates the crop. An oversized image is downscaled on import. Adding an image changes the layout of the text around it rather than covering it. Do not build yet: No export yet. ### Phase 5 · Export, including the file people ask for Build: Export PDF from the same layout function as the preview. Export a presentation format by mapping computed geometry to its shapes and text boxes. Open the result in a real presentation application and record what does not survive. Trap: A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. Done when: A PDF matches the preview exactly for a deck with auto-fit text and a cropped image. The presentation export opens in a real presentation application with text and images in the right places. A written note records what does not survive the presentation export. Exporting twice produces identical files. Do not build yet: Generation from content comes last. ### Phase 6 · Generating a first draft from content Build: Take structured content, an outline or a document, and propose slides by choosing layouts and distributing content into slots. Every generated slide is a normal slide afterwards. Nothing is generated that the layout rules cannot render. Trap: Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. Done when: An outline produces a deck where every slide renders without overflow. Generated slides are editable exactly like hand-made ones. Content exceeding a slot's limit is split across slides rather than overflowing. Regenerating does not overwrite slides that have been edited. Do not build yet: Nothing after this. ### Out of scope (and why) - The smart template library, which is the product's actual value and is accumulated design work rather than code. - Brand controls and shared libraries across an organisation, which is the reason teams buy it. - Real-time collaborative editing. - Animation and transitions, which are mostly lost in export anyway and are a different kind of work. ### README must contain - That layout is computed and no position is ever stored - That auto-fit measures with the real font and stops at a readable floor - That themes are referenced and slides contain no literal colours or sizes - That the presentation format export is lossy, with the recorded list of what does not survive - That generation is constrained to what the layouts can render ===== AGENTS.md ===== # Agent instructions · Beautiful.ai indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, Rules computing positions from content, never free positioning, SVG for the editor and for PDF, through one layout function, PDF from the same layout, plus a presentation format for people who need the file, Measured with the real font before layout. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ===== BUILD_PLAN.md ===== # Build plan · Beautiful.ai Slides defined as structured content bound to layout rules that compute positions, text that auto-fits within readable bounds, a theme applied by reference so a brand change updates every slide, and export to PDF and to a presentation format that opens correctly elsewhere. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One layout that computes itself A slide reflows when its content changes, without anyone moving anything. ### Steps 1. Define layouts as data Slots and rules. Written down before implementation, like any format that others will extend. Files: `docs/layout-format.md` 2. Write the layout function pure Content and theme in, geometry out. No document object model access, so it is testable directly. Files: `src/layout.ts` 3. Never store positions Grep the schema. A stored position is the thing that breaks reflow. Files: `src/model.ts` ### Done when - [ ] Adding a bullet rebalances rather than overflowing - [ ] No position is stored anywhere - [ ] The layout function is tested without a browser - [ ] Excess content is reported rather than overflowing ### Watch out - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. ## Phase 2 · Text that fits and stays readable Auto-fit shrinks text to fit and stops before it becomes unreadable. ### Steps 1. Load fonts before measuring Await font loading, then measure. Re-measure on load completion if you measured early. Files: `src/text.ts` 2. Binary search to fit with a floor Below the floor is a content problem, not a sizing problem, and the layout should say so. Files: `src/autofit.ts` 3. Verify on a cold load A profile without the font cached. This is where a fallback measurement shows up. ### Done when - [ ] Auto-fit stops at a readable floor and reports overflow - [ ] Measurement waits for the real font - [ ] A cold load produces the same sizes - [ ] Preview and export wrap identically ### Watch out - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. ## Phase 3 · Themes applied by reference Changing the brand updates every slide at once. ### Steps 1. Model themes as referenced values Palette, fonts, spacing. Layouts read from the theme rather than embedding values. Files: `src/theme.ts` 2. Reflow on theme change Because font metrics changed, auto-fit must run again across the deck. Files: `src/deck.ts` 3. Forbid literal overrides Or make them named variants. This is the rule that keeps the premise intact. Files: `src/model.ts` ### Done when - [ ] A theme colour change updates every slide - [ ] A theme font change reflows the deck correctly - [ ] No literal colours or sizes are stored in slides - [ ] Switching themes on a finished deck works ### Watch out - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. ## Phase 4 · Images without ruining the layout An image of any shape fits its slot without distortion or a decapitated subject. ### Steps 1. Crop with a focal point Cover fit anchored to the focal point, adjustable, defaulting to centre. Files: `src/image.ts` 2. Downscale on import A twelve megapixel photo in a slide is wasted bytes and a slow editor. Files: `src/assets.ts` 3. Let images affect layout The layout rule accounts for the image slot. An image placed over text is the thing this product avoids. Files: `src/layout.ts` ### Done when - [ ] A portrait in a wide slot keeps its focal point - [ ] Changing the focal point updates the crop - [ ] Oversized images are downscaled on import - [ ] Adding an image reflows surrounding text ### Watch out - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. ## Phase 5 · Export, including the file people ask for A PDF is faithful, and the presentation file opens correctly enough to present from. ### Steps 1. Export PDF from the layout function The same geometry as the preview. Any divergence here is a bug rather than a format difference. Files: `src/export-pdf.ts` 2. Map geometry to the presentation format Absolute boxes with embedded or substituted fonts, accepting the loss. Files: `src/export-pptx.ts` 3. Open it in a real application and record the losses From the first template. This is the check people skip and regret. Files: `docs/export-fidelity.md` ### Done when - [ ] PDF matches the preview exactly - [ ] The presentation export opens correctly in a real application - [ ] The fidelity note records what is lost - [ ] Two exports are identical ### Watch out - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ## Not in this build - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. ## After v1, if you want it - A speaker view with notes and a timer, which needs no new layout work - Chart slots fed from a table, laid out by the same rules - A second theme built from a real brand guideline, which tests whether the theme model is general ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. VITE_AUTOFIT_MIN_PT=14 # Required. Longest edge kept on import. VITE_MAX_IMAGE_PX=3000 # Required. Self-hosted fonts with licences beside them. VITE_FONT_DIR=/fonts # Required. Rasterisation density for images in exports. VITE_EXPORT_DPI=150 # Required. Layout definitions as data. Adding a layout is a file here. VITE_LAYOUT_DIR=/layouts
You are building a production product version of Beautiful.ai. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Beautiful.ai · product brief ## Problem You can build a template deck generator, but auto-layout slide behavior, template polish, brand controls, PowerPoint import/export, and collaboration are real work. ## Product outcome A deck tool where adding content reflows the slide rather than breaking it, a brand change updates everything, and the exported file opens correctly where your audience will open it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - LLM API - slide layout engine - PPTX/PDF export library - template library - hosted app ## Explicit non-goals for v1 - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. - smart templates - brand controls - shared libraries - PowerPoint compatibility - collaboration - viewer analytics ## Success criteria - Fixture decks export byte-identically to their references - Auto-fit is confirmed to stop at the readable floor - No literal colour or size is stored in any slide - The presentation export is confirmed to open correctly in a real application - The export fidelity note is current ===== BRIEF.md ===== # Build brief · Beautiful.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me slides generated from structured content, where the layout adapts instead of you nudging boxes to replace Beautiful.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The product here is that layout is computed rather than drawn. A slide is content plus a layout rule, and adding a bullet or a longer heading reflows the slide instead of overflowing it. If you find yourself building free positioning with snapping, you have built a different and much larger product. The second constraint is export. A deck that looks right in your renderer and wrong in PowerPoint is a deck nobody can present, because someone will always need the file. Test export fidelity from the first template rather than after building twenty. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite · the layout engine is the product and it is a pure function you want to test - Layout: Rules computing positions from content, never free positioning · this is the entire premise; free positioning is a different product - Rendering: SVG for the editor and for PDF, through one layout function · one geometry source so the preview and the export cannot disagree - Export: PDF from the same layout, plus a presentation format for people who need the file · PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both - Text: Measured with the real font before layout · auto-fit against a fallback font produces text that overflows once the real face loads ### Data model (create this before Phase 1) - decks(id, title, theme_id, created_at, updated_at) - slides(id, deck_id, ordinal, layout_key, content_json, notes) - layouts(key, name, slots_json, rules_json) · slots describe what content a layout accepts and rules compute geometry from it - themes(id, name, palette_json, fonts_json, spacing_json) - assets(id, deck_id, kind, path, bytes, focal_x, focal_y) - Content is stored as structure rather than as positioned boxes. Position is always derived, which is what makes reflow possible. ### Phase 1 · One layout that computes itself Build: Define one layout as data: the slots it accepts and the rules that turn content into geometry. Render it. Add a bullet and watch the slide rebalance. Write the layout function as pure, so it is testable without a browser. Trap: Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. Done when: Adding a fifth bullet to a four bullet slide rebalances spacing without overflow. No slide stores a position, verified by inspecting the schema. The layout function is pure and tested without a browser. A slot given more content than the layout can hold reports that rather than overflowing. Do not build yet: No themes, text fitting, images or export. ### Phase 2 · Text that fits and stays readable Build: Measure text with the real font loaded. Binary search the size until wrapped text fits its slot, with a floor below which the layout reports that the content is too long instead of shrinking further. Trap: Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: A long heading shrinks to fit and stops at the readable floor, reporting that the content is too long rather than continuing. Measurement waits for the real font. The same content produces the same size on a machine that does not have the font cached, verified by a cold load. Wrapping matches between preview and export. Do not build yet: No themes or images yet. ### Phase 3 · Themes applied by reference Build: A theme of palette, fonts and spacing. Layouts reference theme values rather than literal colours or sizes. Changing the theme reflows and recolours the whole deck without touching slide content. Trap: Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. Done when: Changing a theme colour updates every slide using it. Changing the theme font reflows every slide with correct auto-fit. No slide contains a literal colour or font size, verified by inspecting stored content. Switching themes on a finished deck produces a correct deck rather than broken layouts. Do not build yet: No images or export yet. ### Phase 4 · Images without ruining the layout Build: Image slots with a fit mode and a focal point. Crop to the slot's computed shape using the focal point. Downscale on import. The layout adapts to the image's presence rather than the image being placed on top of it. Trap: Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. Done when: A portrait image in a wide slot keeps its focal point visible. Changing the focal point updates the crop. An oversized image is downscaled on import. Adding an image changes the layout of the text around it rather than covering it. Do not build yet: No export yet. ### Phase 5 · Export, including the file people ask for Build: Export PDF from the same layout function as the preview. Export a presentation format by mapping computed geometry to its shapes and text boxes. Open the result in a real presentation application and record what does not survive. Trap: A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. Done when: A PDF matches the preview exactly for a deck with auto-fit text and a cropped image. The presentation export opens in a real presentation application with text and images in the right places. A written note records what does not survive the presentation export. Exporting twice produces identical files. Do not build yet: Generation from content comes last. ### Phase 6 · Generating a first draft from content Build: Take structured content, an outline or a document, and propose slides by choosing layouts and distributing content into slots. Every generated slide is a normal slide afterwards. Nothing is generated that the layout rules cannot render. Trap: Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. Done when: An outline produces a deck where every slide renders without overflow. Generated slides are editable exactly like hand-made ones. Content exceeding a slot's limit is split across slides rather than overflowing. Regenerating does not overwrite slides that have been edited. Do not build yet: Nothing after this. ### Out of scope (and why) - The smart template library, which is the product's actual value and is accumulated design work rather than code. - Brand controls and shared libraries across an organisation, which is the reason teams buy it. - Real-time collaborative editing. - Animation and transitions, which are mostly lost in export anyway and are a different kind of work. ### README must contain - That layout is computed and no position is ever stored - That auto-fit measures with the real font and stops at a readable floor - That themes are referenced and slides contain no literal colours or sizes - That the presentation format export is lossy, with the recorded list of what does not survive - That generation is constrained to what the layouts can render ===== ARCHITECTURE.md ===== # Architecture · Beautiful.ai ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite | the layout engine is the product and it is a pure function you want to test | | Layout | Rules computing positions from content, never free positioning | this is the entire premise; free positioning is a different product | | Rendering | SVG for the editor and for PDF, through one layout function | one geometry source so the preview and the export cannot disagree | | Export | PDF from the same layout, plus a presentation format for people who need the file | PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both | | Text | Measured with the real font before layout | auto-fit against a fallback font produces text that overflows once the real face loads | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Layout | Turning content and theme into geometry. | This is the product. A pure function is what makes it testable and what keeps preview and export identical. | | Text | Measurement and auto-fit. | Any measurement approach that waits for the real font. The readable floor is a product decision rather than a limit. | | Theme | Referenced palette, fonts and spacing. | Forbidding literal overrides is what keeps a brand change meaningful. | | Export | PDF and presentation format. | PDF is faithful; the presentation format is lossy and that loss must be documented rather than discovered. | | Generation | Proposing slides from content. | Optional. Constraining it to layout slots is what keeps output renderable. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_AUTOFIT_MIN_PT` · required · Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. - `VITE_MAX_IMAGE_PX` · required · Longest edge kept on import. - `VITE_FONT_DIR` · required · Self-hosted fonts with licences beside them. - `VITE_EXPORT_DPI` · required · Rasterisation density for images in exports. - `VITE_LAYOUT_DIR` · required · Layout definitions as data. Adding a layout is a file here. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Beautiful.ai product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, Rules computing positions from content, never free positioning, SVG for the editor and for PDF, through one layout function, PDF from the same layout, plus a presentation format for people who need the file, Measured with the real font before layout. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. - Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. ===== MILESTONES.md ===== # Delivery milestones · Beautiful.ai Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · One layout that computes itself A slide reflows when its content changes, without anyone moving anything. ### Steps 1. Define layouts as data Slots and rules. Written down before implementation, like any format that others will extend. Files: `docs/layout-format.md` 2. Write the layout function pure Content and theme in, geometry out. No document object model access, so it is testable directly. Files: `src/layout.ts` 3. Never store positions Grep the schema. A stored position is the thing that breaks reflow. Files: `src/model.ts` ### Done when - [ ] Adding a bullet rebalances rather than overflowing - [ ] No position is stored anywhere - [ ] The layout function is tested without a browser - [ ] Excess content is reported rather than overflowing ### Watch out - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. ## M2 · Text that fits and stays readable Auto-fit shrinks text to fit and stops before it becomes unreadable. ### Steps 1. Load fonts before measuring Await font loading, then measure. Re-measure on load completion if you measured early. Files: `src/text.ts` 2. Binary search to fit with a floor Below the floor is a content problem, not a sizing problem, and the layout should say so. Files: `src/autofit.ts` 3. Verify on a cold load A profile without the font cached. This is where a fallback measurement shows up. ### Done when - [ ] Auto-fit stops at a readable floor and reports overflow - [ ] Measurement waits for the real font - [ ] A cold load produces the same sizes - [ ] Preview and export wrap identically ### Watch out - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. ## M3 · Themes applied by reference Changing the brand updates every slide at once. ### Steps 1. Model themes as referenced values Palette, fonts, spacing. Layouts read from the theme rather than embedding values. Files: `src/theme.ts` 2. Reflow on theme change Because font metrics changed, auto-fit must run again across the deck. Files: `src/deck.ts` 3. Forbid literal overrides Or make them named variants. This is the rule that keeps the premise intact. Files: `src/model.ts` ### Done when - [ ] A theme colour change updates every slide - [ ] A theme font change reflows the deck correctly - [ ] No literal colours or sizes are stored in slides - [ ] Switching themes on a finished deck works ### Watch out - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. ## M4 · Images without ruining the layout An image of any shape fits its slot without distortion or a decapitated subject. ### Steps 1. Crop with a focal point Cover fit anchored to the focal point, adjustable, defaulting to centre. Files: `src/image.ts` 2. Downscale on import A twelve megapixel photo in a slide is wasted bytes and a slow editor. Files: `src/assets.ts` 3. Let images affect layout The layout rule accounts for the image slot. An image placed over text is the thing this product avoids. Files: `src/layout.ts` ### Done when - [ ] A portrait in a wide slot keeps its focal point - [ ] Changing the focal point updates the crop - [ ] Oversized images are downscaled on import - [ ] Adding an image reflows surrounding text ### Watch out - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. ## M5 · Export, including the file people ask for A PDF is faithful, and the presentation file opens correctly enough to present from. ### Steps 1. Export PDF from the layout function The same geometry as the preview. Any divergence here is a bug rather than a format difference. Files: `src/export-pdf.ts` 2. Map geometry to the presentation format Absolute boxes with embedded or substituted fonts, accepting the loss. Files: `src/export-pptx.ts` 3. Open it in a real application and record the losses From the first template. This is the check people skip and regret. Files: `docs/export-fidelity.md` ### Done when - [ ] PDF matches the preview exactly - [ ] The presentation export opens correctly in a real application - [ ] The fidelity note records what is lost - [ ] Two exports are identical ### Watch out - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ## M6 · Generating a first draft from content (production only) An outline becomes a deck you edit rather than a deck you start from nothing. ### Steps 1. Constrain generation to slots Including length limits. Generation that ignores the layout produces unrenderable slides. Files: `src/generate.ts` 2. Split rather than overflow Content too long for a slot becomes two slides, which is what a person would do. Files: `src/generate.ts` 3. Protect edited slides Mark edited slides and leave them alone on regeneration. Files: `src/generate.ts` ### Done when - [ ] A generated deck renders with no overflow - [ ] Generated slides are editable like any other - [ ] Overlong content splits across slides - [ ] Edited slides survive regeneration ### Watch out - Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. ===== OPERATIONS.md ===== # Operations · Beautiful.ai ## Backup Decks are stored content plus a theme reference, which is small. The assets are larger and are the part worth backing up separately. ## Restore Restore decks and assets, then export one to PDF and compare against a stored reference. That comparison is what proves the restore rather than the deck merely opening. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing server-side. Track export determinism in continuous integration against fixture decks, since a font or layout change that shifts output is otherwise found by a colleague mid-presentation. ## Incident checklist A font update changing metrics reflows every deck. Pin font versions and treat a font change as a release with a visual diff across fixtures. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Fixture decks export byte-identically to their references - [ ] Auto-fit is confirmed to stop at the readable floor - [ ] No literal colour or size is stored in any slide - [ ] The presentation export is confirmed to open correctly in a real application - [ ] The export fidelity note is current ## Launch constraint Do not market omitted Beautiful.ai capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. VITE_AUTOFIT_MIN_PT=14 # Required. Longest edge kept on import. VITE_MAX_IMAGE_PX=3000 # Required. Self-hosted fonts with licences beside them. VITE_FONT_DIR=/fonts # Required. Rasterisation density for images in exports. VITE_EXPORT_DPI=150 # Required. Layout definitions as data. Adding a layout is a file here. VITE_LAYOUT_DIR=/layouts
# Beautiful.ai · indie build Slides defined as structured content bound to layout rules that compute positions, text that auto-fits within readable bounds, a theme applied by reference so a brand change updates every slide, and export to PDF and to a presentation format that opens correctly elsewhere. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite | the layout engine is the product and it is a pure function you want to test | | Layout | Rules computing positions from content, never free positioning | this is the entire premise; free positioning is a different product | | Rendering | SVG for the editor and for PDF, through one layout function | one geometry source so the preview and the export cannot disagree | | Export | PDF from the same layout, plus a presentation format for people who need the file | PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both | | Text | Measured with the real font before layout | auto-fit against a fallback font produces text that overflows once the real face loads | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build and the export pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real presentation application to test export** · varies; a free alternative is adequate for a fidelity check - Why: Phase 5 is only meaningful if you open the exported file where your audience will. Your own renderer agreeing with itself proves nothing. - Get it: Whichever your colleagues use. If several, test in each, because they differ. - Verify: An exported deck opens with text and images in the right places - [ ] **Fonts you are licensed to embed** · free with open licences - Why: Both exports embed or substitute fonts, and substitution is what breaks a deck on someone else's machine. - Get it: Openly licensed families with their licence files kept alongside. - Verify: Each bundled font permits embedding - [ ] **A real deck you have given before** · free - Why: Layout rules are only testable against real content, which has the long headings and awkward bullet counts that invented content does not. - Get it: Take a deck you actually presented and rebuild it. The slides that resist are where the layout rules need work. - Verify: A real deck of at least ten slides is rebuilt - [ ] **Test images including a tall portrait** · free - Why: Phase 4's focal point handling cannot be judged without an image whose subject is not centred. - Get it: Any photo library. One tall portrait with a face near the top is the case that matters. - Verify: The portrait is available as a fixture ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. - smart templates - brand controls - shared libraries - PowerPoint compatibility - collaboration - viewer analytics If one of those is essential to you, that is the reason to keep paying for Beautiful.ai, and the README should say so rather than pretend.
# Build brief · Beautiful.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me slides generated from structured content, where the layout adapts instead of you nudging boxes to replace Beautiful.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The product here is that layout is computed rather than drawn. A slide is content plus a layout rule, and adding a bullet or a longer heading reflows the slide instead of overflowing it. If you find yourself building free positioning with snapping, you have built a different and much larger product. The second constraint is export. A deck that looks right in your renderer and wrong in PowerPoint is a deck nobody can present, because someone will always need the file. Test export fidelity from the first template rather than after building twenty. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite · the layout engine is the product and it is a pure function you want to test - Layout: Rules computing positions from content, never free positioning · this is the entire premise; free positioning is a different product - Rendering: SVG for the editor and for PDF, through one layout function · one geometry source so the preview and the export cannot disagree - Export: PDF from the same layout, plus a presentation format for people who need the file · PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both - Text: Measured with the real font before layout · auto-fit against a fallback font produces text that overflows once the real face loads ### Data model (create this before Phase 1) - decks(id, title, theme_id, created_at, updated_at) - slides(id, deck_id, ordinal, layout_key, content_json, notes) - layouts(key, name, slots_json, rules_json) · slots describe what content a layout accepts and rules compute geometry from it - themes(id, name, palette_json, fonts_json, spacing_json) - assets(id, deck_id, kind, path, bytes, focal_x, focal_y) - Content is stored as structure rather than as positioned boxes. Position is always derived, which is what makes reflow possible. ### Phase 1 · One layout that computes itself Build: Define one layout as data: the slots it accepts and the rules that turn content into geometry. Render it. Add a bullet and watch the slide rebalance. Write the layout function as pure, so it is testable without a browser. Trap: Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. Done when: Adding a fifth bullet to a four bullet slide rebalances spacing without overflow. No slide stores a position, verified by inspecting the schema. The layout function is pure and tested without a browser. A slot given more content than the layout can hold reports that rather than overflowing. Do not build yet: No themes, text fitting, images or export. ### Phase 2 · Text that fits and stays readable Build: Measure text with the real font loaded. Binary search the size until wrapped text fits its slot, with a floor below which the layout reports that the content is too long instead of shrinking further. Trap: Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: A long heading shrinks to fit and stops at the readable floor, reporting that the content is too long rather than continuing. Measurement waits for the real font. The same content produces the same size on a machine that does not have the font cached, verified by a cold load. Wrapping matches between preview and export. Do not build yet: No themes or images yet. ### Phase 3 · Themes applied by reference Build: A theme of palette, fonts and spacing. Layouts reference theme values rather than literal colours or sizes. Changing the theme reflows and recolours the whole deck without touching slide content. Trap: Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. Done when: Changing a theme colour updates every slide using it. Changing the theme font reflows every slide with correct auto-fit. No slide contains a literal colour or font size, verified by inspecting stored content. Switching themes on a finished deck produces a correct deck rather than broken layouts. Do not build yet: No images or export yet. ### Phase 4 · Images without ruining the layout Build: Image slots with a fit mode and a focal point. Crop to the slot's computed shape using the focal point. Downscale on import. The layout adapts to the image's presence rather than the image being placed on top of it. Trap: Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. Done when: A portrait image in a wide slot keeps its focal point visible. Changing the focal point updates the crop. An oversized image is downscaled on import. Adding an image changes the layout of the text around it rather than covering it. Do not build yet: No export yet. ### Phase 5 · Export, including the file people ask for Build: Export PDF from the same layout function as the preview. Export a presentation format by mapping computed geometry to its shapes and text boxes. Open the result in a real presentation application and record what does not survive. Trap: A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. Done when: A PDF matches the preview exactly for a deck with auto-fit text and a cropped image. The presentation export opens in a real presentation application with text and images in the right places. A written note records what does not survive the presentation export. Exporting twice produces identical files. Do not build yet: Generation from content comes last. ### Phase 6 · Generating a first draft from content Build: Take structured content, an outline or a document, and propose slides by choosing layouts and distributing content into slots. Every generated slide is a normal slide afterwards. Nothing is generated that the layout rules cannot render. Trap: Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. Done when: An outline produces a deck where every slide renders without overflow. Generated slides are editable exactly like hand-made ones. Content exceeding a slot's limit is split across slides rather than overflowing. Regenerating does not overwrite slides that have been edited. Do not build yet: Nothing after this. ### Out of scope (and why) - The smart template library, which is the product's actual value and is accumulated design work rather than code. - Brand controls and shared libraries across an organisation, which is the reason teams buy it. - Real-time collaborative editing. - Animation and transitions, which are mostly lost in export anyway and are a different kind of work. ### README must contain - That layout is computed and no position is ever stored - That auto-fit measures with the real font and stops at a readable floor - That themes are referenced and slides contain no literal colours or sizes - That the presentation format export is lossy, with the recorded list of what does not survive - That generation is constrained to what the layouts can render
# Agent instructions · Beautiful.ai indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, Rules computing positions from content, never free positioning, SVG for the editor and for PDF, through one layout function, PDF from the same layout, plus a presentation format for people who need the file, Measured with the real font before layout. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting.
# Build plan · Beautiful.ai Slides defined as structured content bound to layout rules that compute positions, text that auto-fits within readable bounds, a theme applied by reference so a brand change updates every slide, and export to PDF and to a presentation format that opens correctly elsewhere. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One layout that computes itself A slide reflows when its content changes, without anyone moving anything. ### Steps 1. Define layouts as data Slots and rules. Written down before implementation, like any format that others will extend. Files: `docs/layout-format.md` 2. Write the layout function pure Content and theme in, geometry out. No document object model access, so it is testable directly. Files: `src/layout.ts` 3. Never store positions Grep the schema. A stored position is the thing that breaks reflow. Files: `src/model.ts` ### Done when - [ ] Adding a bullet rebalances rather than overflowing - [ ] No position is stored anywhere - [ ] The layout function is tested without a browser - [ ] Excess content is reported rather than overflowing ### Watch out - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. ## Phase 2 · Text that fits and stays readable Auto-fit shrinks text to fit and stops before it becomes unreadable. ### Steps 1. Load fonts before measuring Await font loading, then measure. Re-measure on load completion if you measured early. Files: `src/text.ts` 2. Binary search to fit with a floor Below the floor is a content problem, not a sizing problem, and the layout should say so. Files: `src/autofit.ts` 3. Verify on a cold load A profile without the font cached. This is where a fallback measurement shows up. ### Done when - [ ] Auto-fit stops at a readable floor and reports overflow - [ ] Measurement waits for the real font - [ ] A cold load produces the same sizes - [ ] Preview and export wrap identically ### Watch out - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. ## Phase 3 · Themes applied by reference Changing the brand updates every slide at once. ### Steps 1. Model themes as referenced values Palette, fonts, spacing. Layouts read from the theme rather than embedding values. Files: `src/theme.ts` 2. Reflow on theme change Because font metrics changed, auto-fit must run again across the deck. Files: `src/deck.ts` 3. Forbid literal overrides Or make them named variants. This is the rule that keeps the premise intact. Files: `src/model.ts` ### Done when - [ ] A theme colour change updates every slide - [ ] A theme font change reflows the deck correctly - [ ] No literal colours or sizes are stored in slides - [ ] Switching themes on a finished deck works ### Watch out - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. ## Phase 4 · Images without ruining the layout An image of any shape fits its slot without distortion or a decapitated subject. ### Steps 1. Crop with a focal point Cover fit anchored to the focal point, adjustable, defaulting to centre. Files: `src/image.ts` 2. Downscale on import A twelve megapixel photo in a slide is wasted bytes and a slow editor. Files: `src/assets.ts` 3. Let images affect layout The layout rule accounts for the image slot. An image placed over text is the thing this product avoids. Files: `src/layout.ts` ### Done when - [ ] A portrait in a wide slot keeps its focal point - [ ] Changing the focal point updates the crop - [ ] Oversized images are downscaled on import - [ ] Adding an image reflows surrounding text ### Watch out - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. ## Phase 5 · Export, including the file people ask for A PDF is faithful, and the presentation file opens correctly enough to present from. ### Steps 1. Export PDF from the layout function The same geometry as the preview. Any divergence here is a bug rather than a format difference. Files: `src/export-pdf.ts` 2. Map geometry to the presentation format Absolute boxes with embedded or substituted fonts, accepting the loss. Files: `src/export-pptx.ts` 3. Open it in a real application and record the losses From the first template. This is the check people skip and regret. Files: `docs/export-fidelity.md` ### Done when - [ ] PDF matches the preview exactly - [ ] The presentation export opens correctly in a real application - [ ] The fidelity note records what is lost - [ ] Two exports are identical ### Watch out - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ## Not in this build - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. ## After v1, if you want it - A speaker view with notes and a timer, which needs no new layout work - Chart slots fed from a table, laid out by the same rules - A second theme built from a real brand guideline, which tests whether the theme model is general
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. VITE_AUTOFIT_MIN_PT=14 # Required. Longest edge kept on import. VITE_MAX_IMAGE_PX=3000 # Required. Self-hosted fonts with licences beside them. VITE_FONT_DIR=/fonts # Required. Rasterisation density for images in exports. VITE_EXPORT_DPI=150 # Required. Layout definitions as data. Adding a layout is a file here. VITE_LAYOUT_DIR=/layouts
# Beautiful.ai · product brief ## Problem You can build a template deck generator, but auto-layout slide behavior, template polish, brand controls, PowerPoint import/export, and collaboration are real work. ## Product outcome A deck tool where adding content reflows the slide rather than breaking it, a brand change updates everything, and the exported file opens correctly where your audience will open it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - LLM API - slide layout engine - PPTX/PDF export library - template library - hosted app ## Explicit non-goals for v1 - The smart template library. - Organisational brand controls and shared libraries. - Real-time collaboration. - Animation and transitions. - smart templates - brand controls - shared libraries - PowerPoint compatibility - collaboration - viewer analytics ## Success criteria - Fixture decks export byte-identically to their references - Auto-fit is confirmed to stop at the readable floor - No literal colour or size is stored in any slide - The presentation export is confirmed to open correctly in a real application - The export fidelity note is current
# Build brief · Beautiful.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me slides generated from structured content, where the layout adapts instead of you nudging boxes to replace Beautiful.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The product here is that layout is computed rather than drawn. A slide is content plus a layout rule, and adding a bullet or a longer heading reflows the slide instead of overflowing it. If you find yourself building free positioning with snapping, you have built a different and much larger product. The second constraint is export. A deck that looks right in your renderer and wrong in PowerPoint is a deck nobody can present, because someone will always need the file. Test export fidelity from the first template rather than after building twenty. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite · the layout engine is the product and it is a pure function you want to test - Layout: Rules computing positions from content, never free positioning · this is the entire premise; free positioning is a different product - Rendering: SVG for the editor and for PDF, through one layout function · one geometry source so the preview and the export cannot disagree - Export: PDF from the same layout, plus a presentation format for people who need the file · PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both - Text: Measured with the real font before layout · auto-fit against a fallback font produces text that overflows once the real face loads ### Data model (create this before Phase 1) - decks(id, title, theme_id, created_at, updated_at) - slides(id, deck_id, ordinal, layout_key, content_json, notes) - layouts(key, name, slots_json, rules_json) · slots describe what content a layout accepts and rules compute geometry from it - themes(id, name, palette_json, fonts_json, spacing_json) - assets(id, deck_id, kind, path, bytes, focal_x, focal_y) - Content is stored as structure rather than as positioned boxes. Position is always derived, which is what makes reflow possible. ### Phase 1 · One layout that computes itself Build: Define one layout as data: the slots it accepts and the rules that turn content into geometry. Render it. Add a bullet and watch the slide rebalance. Write the layout function as pure, so it is testable without a browser. Trap: Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. Done when: Adding a fifth bullet to a four bullet slide rebalances spacing without overflow. No slide stores a position, verified by inspecting the schema. The layout function is pure and tested without a browser. A slot given more content than the layout can hold reports that rather than overflowing. Do not build yet: No themes, text fitting, images or export. ### Phase 2 · Text that fits and stays readable Build: Measure text with the real font loaded. Binary search the size until wrapped text fits its slot, with a floor below which the layout reports that the content is too long instead of shrinking further. Trap: Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. Done when: A long heading shrinks to fit and stops at the readable floor, reporting that the content is too long rather than continuing. Measurement waits for the real font. The same content produces the same size on a machine that does not have the font cached, verified by a cold load. Wrapping matches between preview and export. Do not build yet: No themes or images yet. ### Phase 3 · Themes applied by reference Build: A theme of palette, fonts and spacing. Layouts reference theme values rather than literal colours or sizes. Changing the theme reflows and recolours the whole deck without touching slide content. Trap: Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. Done when: Changing a theme colour updates every slide using it. Changing the theme font reflows every slide with correct auto-fit. No slide contains a literal colour or font size, verified by inspecting stored content. Switching themes on a finished deck produces a correct deck rather than broken layouts. Do not build yet: No images or export yet. ### Phase 4 · Images without ruining the layout Build: Image slots with a fit mode and a focal point. Crop to the slot's computed shape using the focal point. Downscale on import. The layout adapts to the image's presence rather than the image being placed on top of it. Trap: Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. Done when: A portrait image in a wide slot keeps its focal point visible. Changing the focal point updates the crop. An oversized image is downscaled on import. Adding an image changes the layout of the text around it rather than covering it. Do not build yet: No export yet. ### Phase 5 · Export, including the file people ask for Build: Export PDF from the same layout function as the preview. Export a presentation format by mapping computed geometry to its shapes and text boxes. Open the result in a real presentation application and record what does not survive. Trap: A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. Done when: A PDF matches the preview exactly for a deck with auto-fit text and a cropped image. The presentation export opens in a real presentation application with text and images in the right places. A written note records what does not survive the presentation export. Exporting twice produces identical files. Do not build yet: Generation from content comes last. ### Phase 6 · Generating a first draft from content Build: Take structured content, an outline or a document, and propose slides by choosing layouts and distributing content into slots. Every generated slide is a normal slide afterwards. Nothing is generated that the layout rules cannot render. Trap: Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable. Done when: An outline produces a deck where every slide renders without overflow. Generated slides are editable exactly like hand-made ones. Content exceeding a slot's limit is split across slides rather than overflowing. Regenerating does not overwrite slides that have been edited. Do not build yet: Nothing after this. ### Out of scope (and why) - The smart template library, which is the product's actual value and is accumulated design work rather than code. - Brand controls and shared libraries across an organisation, which is the reason teams buy it. - Real-time collaborative editing. - Animation and transitions, which are mostly lost in export anyway and are a different kind of work. ### README must contain - That layout is computed and no position is ever stored - That auto-fit measures with the real font and stops at a readable floor - That themes are referenced and slides contain no literal colours or sizes - That the presentation format export is lossy, with the recorded list of what does not survive - That generation is constrained to what the layouts can render
# Architecture · Beautiful.ai ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite | the layout engine is the product and it is a pure function you want to test | | Layout | Rules computing positions from content, never free positioning | this is the entire premise; free positioning is a different product | | Rendering | SVG for the editor and for PDF, through one layout function | one geometry source so the preview and the export cannot disagree | | Export | PDF from the same layout, plus a presentation format for people who need the file | PDF is faithful and unopenable for editing; the presentation format is editable and lossy, and you need both | | Text | Measured with the real font before layout | auto-fit against a fallback font produces text that overflows once the real face loads | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Layout | Turning content and theme into geometry. | This is the product. A pure function is what makes it testable and what keeps preview and export identical. | | Text | Measurement and auto-fit. | Any measurement approach that waits for the real font. The readable floor is a product decision rather than a limit. | | Theme | Referenced palette, fonts and spacing. | Forbidding literal overrides is what keeps a brand change meaningful. | | Export | PDF and presentation format. | PDF is faithful; the presentation format is lossy and that loss must be documented rather than discovered. | | Generation | Proposing slides from content. | Optional. Constraining it to layout slots is what keeps output renderable. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_AUTOFIT_MIN_PT` · required · Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. - `VITE_MAX_IMAGE_PX` · required · Longest edge kept on import. - `VITE_FONT_DIR` · required · Self-hosted fonts with licences beside them. - `VITE_EXPORT_DPI` · required · Rasterisation density for images in exports. - `VITE_LAYOUT_DIR` · required · Layout definitions as data. Adding a layout is a file here. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Beautiful.ai product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, Rules computing positions from content, never free positioning, SVG for the editor and for PDF, through one layout function, PDF from the same layout, plus a presentation format for people who need the file, Measured with the real font before layout. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. - Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable.
# Delivery milestones · Beautiful.ai Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · One layout that computes itself A slide reflows when its content changes, without anyone moving anything. ### Steps 1. Define layouts as data Slots and rules. Written down before implementation, like any format that others will extend. Files: `docs/layout-format.md` 2. Write the layout function pure Content and theme in, geometry out. No document object model access, so it is testable directly. Files: `src/layout.ts` 3. Never store positions Grep the schema. A stored position is the thing that breaks reflow. Files: `src/model.ts` ### Done when - [ ] Adding a bullet rebalances rather than overflowing - [ ] No position is stored anywhere - [ ] The layout function is tested without a browser - [ ] Excess content is reported rather than overflowing ### Watch out - Building an editor with draggable boxes first and adding rules later never works, because the stored positions become the source of truth and the rules have nothing to compute. Start with computed geometry and never store a position. ## M2 · Text that fits and stays readable Auto-fit shrinks text to fit and stops before it becomes unreadable. ### Steps 1. Load fonts before measuring Await font loading, then measure. Re-measure on load completion if you measured early. Files: `src/text.ts` 2. Binary search to fit with a floor Below the floor is a content problem, not a sizing problem, and the layout should say so. Files: `src/autofit.ts` 3. Verify on a cold load A profile without the font cached. This is where a fallback measurement shows up. ### Done when - [ ] Auto-fit stops at a readable floor and reports overflow - [ ] Measurement waits for the real font - [ ] A cold load produces the same sizes - [ ] Preview and export wrap identically ### Watch out - Measuring against a fallback font and rendering with the real one produces text that overflows by a word once the face loads, and the failure appears only on someone else's machine. Wait for font loading before measuring, and re-measure when a font finishes loading. ## M3 · Themes applied by reference Changing the brand updates every slide at once. ### Steps 1. Model themes as referenced values Palette, fonts, spacing. Layouts read from the theme rather than embedding values. Files: `src/theme.ts` 2. Reflow on theme change Because font metrics changed, auto-fit must run again across the deck. Files: `src/deck.ts` 3. Forbid literal overrides Or make them named variants. This is the rule that keeps the premise intact. Files: `src/model.ts` ### Done when - [ ] A theme colour change updates every slide - [ ] A theme font change reflows the deck correctly - [ ] No literal colours or sizes are stored in slides - [ ] Switching themes on a finished deck works ### Watch out - Letting a slide override a theme value with a literal is the feature that destroys the premise, because within a week half the deck is hand-coloured and a brand change no longer works. If overrides are needed, keep them as named theme variants rather than arbitrary values. ## M4 · Images without ruining the layout An image of any shape fits its slot without distortion or a decapitated subject. ### Steps 1. Crop with a focal point Cover fit anchored to the focal point, adjustable, defaulting to centre. Files: `src/image.ts` 2. Downscale on import A twelve megapixel photo in a slide is wasted bytes and a slow editor. Files: `src/assets.ts` 3. Let images affect layout The layout rule accounts for the image slot. An image placed over text is the thing this product avoids. Files: `src/layout.ts` ### Done when - [ ] A portrait in a wide slot keeps its focal point - [ ] Changing the focal point updates the crop - [ ] Oversized images are downscaled on import - [ ] Adding an image reflows surrounding text ### Watch out - Centre-cropping a portrait into a wide slot removes the top of whatever matters, reliably. Store a focal point per image, default it to centre and make it adjustable, which is a small feature that prevents most bad slides. ## M5 · Export, including the file people ask for A PDF is faithful, and the presentation file opens correctly enough to present from. ### Steps 1. Export PDF from the layout function The same geometry as the preview. Any divergence here is a bug rather than a format difference. Files: `src/export-pdf.ts` 2. Map geometry to the presentation format Absolute boxes with embedded or substituted fonts, accepting the loss. Files: `src/export-pptx.ts` 3. Open it in a real application and record the losses From the first template. This is the check people skip and regret. Files: `docs/export-fidelity.md` ### Done when - [ ] PDF matches the preview exactly - [ ] The presentation export opens correctly in a real application - [ ] The fidelity note records what is lost - [ ] Two exports are identical ### Watch out - A presentation format export is inherently lossy: your computed layout becomes absolutely positioned boxes, and fonts, line breaking and effects may all differ in the receiving application. Test in a real one from the first template and write down what is lost, rather than discovering it when someone opens your deck before a meeting. ## M6 · Generating a first draft from content (production only) An outline becomes a deck you edit rather than a deck you start from nothing. ### Steps 1. Constrain generation to slots Including length limits. Generation that ignores the layout produces unrenderable slides. Files: `src/generate.ts` 2. Split rather than overflow Content too long for a slot becomes two slides, which is what a person would do. Files: `src/generate.ts` 3. Protect edited slides Mark edited slides and leave them alone on regeneration. Files: `src/generate.ts` ### Done when - [ ] A generated deck renders with no overflow - [ ] Generated slides are editable like any other - [ ] Overlong content splits across slides - [ ] Edited slides survive regeneration ### Watch out - Generating slide content freely produces slides that overflow their layouts, and then the layout engine is fighting the generator. Constrain generation to the slots each layout accepts, including their length limits, so what comes out is always renderable.
# Operations · Beautiful.ai ## Backup Decks are stored content plus a theme reference, which is small. The assets are larger and are the part worth backing up separately. ## Restore Restore decks and assets, then export one to PDF and compare against a stored reference. That comparison is what proves the restore rather than the deck merely opening. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing server-side. Track export determinism in continuous integration against fixture decks, since a font or layout change that shifts output is otherwise found by a colleague mid-presentation. ## Incident checklist A font update changing metrics reflows every deck. Pin font versions and treat a font change as a release with a visual diff across fixtures. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Fixture decks export byte-identically to their references - [ ] Auto-fit is confirmed to stop at the readable floor - [ ] No literal colour or size is stored in any slide - [ ] The presentation export is confirmed to open correctly in a real application - [ ] The export fidelity note is current ## Launch constraint Do not market omitted Beautiful.ai capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Smallest size auto-fit will produce before reporting the content is too long. Presentation text has a real readability floor. VITE_AUTOFIT_MIN_PT=14 # Required. Longest edge kept on import. VITE_MAX_IMAGE_PX=3000 # Required. Self-hosted fonts with licences beside them. VITE_FONT_DIR=/fonts # Required. Rasterisation density for images in exports. VITE_EXPORT_DPI=150 # Required. Layout definitions as data. Adding a layout is a file here. VITE_LAYOUT_DIR=/layouts
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay for slides that stay designed as content changes.
xsmart templates
xbrand controls
xshared libraries
xPowerPoint compatibility
xcollaboration
xviewer analytics
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Beautiful.ai pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| pro | $45/user | $12/user | 1 user; unlimited slides and AI content; 300+ slide layouts. |
| team | $50/user | $40/user | 2–20 seats; shared slide and template libraries; centralized brand controls. |
| enterprise | custom | — | Designed for more than 20 users; custom onboarding, security and support; price is quote-based. |
free tierno free tier; 14-day card-required trial
billingmonthly + annual; Pro month-to-month is $45, while annual Pro is $12/month billed $144
hidden coststrial auto-renews to the selected paid plan; Team has a 2-seat minimum; annual refunds are narrowly limited and may carry a processing fee; eligible .edu users can receive 1 year of Pro
verified 2026-08-13 · source ↗
Vibecode Beautiful.ai
Kinda. The core of Beautiful.ai is buildable in a weekend with the prompt on this page, but there are real gaps: smart templates, brand controls. Read the honest list above before committing.
How much does Beautiful.ai cost?
Beautiful.ai costs about $45/month (Pro monthly, checked 2026-07-30), which is $540 per year.
What do I lose by replacing Beautiful.ai?
Honestly: smart templates; brand controls; shared libraries; PowerPoint compatibility; collaboration; viewer analytics. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Beautiful.ai?
Yes: Presenton (Generate a deck from a prompt or document, edit it visually, and export editable PPTX or PDF; bring an API key or a local model instead of another subscription.) The prompt is for when you want it exactly your way.