Vibecode Decktopus
track this build4 phases, 12 steps, beginner friendly0%The visible AI presentations loop is buildable, but a credible replacement needs more than the first screen. Decktopus earns its keep through interaction polish, collaboration, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of Decktopus. 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 ===== # Decktopus · indie build A presentation editor whose hard problems are that a slide is a fixed box with no reflow, that generated text has to be measured against that box before it is accepted, and that an export has to reproduce the geometry in the units the target format actually uses. 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 | React with TypeScript on Vite, local first, no account | a deck is a document and editing one should not require a server | | Geometry | Every position and size stored in English Metric Units on a fixed slide canvas | the presentation format measures in them and converting at export is where decks go subtly wrong | | Text | Measured with the real font before it is accepted into a box, never clipped silently | a slide does not reflow, so text either fits or the slide is broken | | Generation | An outline first, then slots with declared character capacity that the model fills | asking for a slide and hoping it fits is how generated decks overflow | | Export | A presentation file written directly, plus PDF, with a report of anything approximated | a deck you cannot open in the tool your audience uses is a picture of a deck | ## 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 development server, the build and the export tests. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Theme fonts you are licensed to embed** · free with open licences - Why: Export fidelity depends on embedding, and a font whose licence forbids it must be substituted deliberately rather than by accident. - Get it: Choose open licensed families and keep the licence files with the project. - Verify: Each theme font has a licence file and an embedding permission noted - [ ] **A mainstream presentation application to check exports in** · free with a viewer - Why: The only meaningful test of an export is opening it in the tool your audience will use. - Get it: Any widely used presentation application, including a free viewer. - Verify: An exported deck opens with boxes at measured positions - [ ] **A model API key for outline and slot generation** (optional) · pay per token; a deck is small change - Why: Generation is the only part that costs money and the only part that leaves the machine. - Get it: A key with a spend limit set in the provider console. - Verify: An outline generates and one slide's slots fill within capacity - [ ] **The slide size you are building for** · free - Why: It sets every layout slot and every capacity figure, and changing it later re-measures the entire deck. - Get it: Pick the aspect ratio and dimensions, confirm them against a file produced by your target application, and write them in the configuration. - Verify: The configured dimensions match a file the target application created ## 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: - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments - large template libraries If one of those is essential to you, that is the reason to keep paying for Decktopus, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Decktopus 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 a deck editor that drafts slides from an outline and refuses to let text overflow the slide it is on to replace Decktopus. 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. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, local first, no account · a deck is a document and editing one should not require a server - Geometry: Every position and size stored in English Metric Units on a fixed slide canvas · the presentation format measures in them and converting at export is where decks go subtly wrong - Text: Measured with the real font before it is accepted into a box, never clipped silently · a slide does not reflow, so text either fits or the slide is broken - Generation: An outline first, then slots with declared character capacity that the model fills · asking for a slide and hoping it fits is how generated decks overflow - Export: A presentation file written directly, plus PDF, with a report of anything approximated · a deck you cannot open in the tool your audience uses is a picture of a deck ### Data model (create this before Phase 1) - decks(id, title, slide_w_emu, slide_h_emu, theme_id, created_at, updated_at) - slides(id, deck_id, position, layout_id, notes, generated_from_outline_item) - boxes(id, slide_id, role, x_emu, y_emu, w_emu, h_emu, z, style_json) · role ties a box to a slot in its layout - content(box_id, kind, text, asset_id, autofit_mode, measured_overflow) · measured_overflow is recomputed on every change and is never allowed to persist as true on a published deck - layouts(id, name, slots_json) · each slot declares its box and a character capacity measured at the theme's sizes - themes(id, name, fonts_json, palette_json, asset_ids_json) · fonts are files stored with the deck - outline(deck_id, position, heading, intent, notes) · the structure the generation fills, editable before any slide exists ### Phase 1 · A slide is a fixed box in real units Build: Fix the slide dimensions per deck and store every geometry value in English Metric Units. Render by converting to screen pixels at draw time, never the other way round. Provide layouts whose slots are defined in the same units. Snapping, alignment and distribution all operate on the stored units so nothing drifts through a round trip. Trap: Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. Done when: Every stored geometry value is an integer in English Metric Units, verified in the schema. Moving a box and then exporting and reimporting returns it to the same stored values, tested for exact equality. Changing the display zoom does not change any stored value, tested. Layout slots are defined in the same units and a slide built from a layout has boxes exactly on the slot geometry. Do not build yet: No text measurement, generation, presenting or export yet. ### Phase 2 · Text that fits, or a slide that admits it does not Build: Measure rendered text against its box using the deck's actual fonts, after those fonts have loaded. Offer shrink to fit with a minimum size below which the text is not shrunk further and the box is marked as overflowing. Show overflow in the editor, in the slide sorter and as a count in the deck header. Recompute on every content, style, box or font change. Trap: Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. Done when: Measurement happens only after the deck's fonts report ready, tested by delaying font loading. A box that cannot fit at the minimum size is marked as overflowing and the count appears in the header, tested. Changing the theme re-measures every box in the deck. No rendering path clips text without the box being marked as overflowing, verified by review. Do not build yet: No generation, presenting or export yet. ### Phase 3 · Generation that fills a structure Build: Generate an editable outline first: headings and intent per slide, no visuals. Then, per slide, ask for structured content against the chosen layout's slots, giving each slot its measured character capacity. Validate the response, measure it, and regenerate that slot once if it overflows. If it still overflows, leave the slot empty and say why rather than pasting it in. Trap: Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. Done when: The outline is editable and regenerating a slide never changes the outline, tested. Every generated slot's text is measured before insertion and an overflowing response is retried once then rejected, tested with a response that is far too long. A rejected slot is visibly empty with its reason shown rather than silently filled. The model identifier and the prompt version are stored with every generated slide. Do not build yet: No presenter view or export yet. ### Phase 4 · Presenting it in a room Build: Full screen presenting with keyboard and clicker navigation, a presenter view on a second screen showing notes, the next slide and elapsed time, and a black or white screen key. Preload the next slide's assets. Make the presenting view work from the local file with no network at all, because the room's connection is not your problem to solve at the time. Trap: Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. Done when: Presenting works with the network disconnected after the deck is open, tested. Presenter view shows notes, the next slide and elapsed time on a second display and stays in sync. Assets are preloaded before presenting starts, with a progress indicator, tested with throttled loading. Keyboard, clicker and blank screen keys all work and are documented on one help overlay. Do not build yet: No export yet. ### Phase 5 · A file other people can open Build: Write the presentation package directly from the stored units, embedding the theme fonts where their licences allow and substituting with a named fallback where they do not. Export PDF from the same geometry. Produce a fidelity report naming every substitution, approximation and unsupported effect. Refuse to export a deck with unresolved overflow without an explicit override. Trap: Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. Done when: An exported presentation opens in a mainstream presentation application with boxes at the same positions, checked by measuring a known slide. Exporting a deck with overflowing boxes requires an explicit override and the override is recorded in the fidelity report. The report names every font substitution and unsupported effect. A PDF export and the presentation export place the same box at the same position on the page, compared numerically. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time co-editing, which is a different application architecture and not something to bolt on later. - Importing other people's presentation files, which is a large compatibility project and will disappoint on the first real deck. - Animations and slide transitions, which do not survive export cleanly and are rarely the reason a talk works. - Embedded forms and viewer analytics, which are what the paid product sells to marketers and are a tracking product rather than an editor. ### README must contain - That geometry is stored in the units the presentation format uses, and the conversion factor with the standard it comes from - That text is measured against its box with the real fonts after they load, and that overflow is a state rather than a clip - That generation fills slots with measured capacities and leaves a slot empty rather than overflowing it - That export is blocked while any box overflows, and how the override is recorded - Which fonts are embedded, which are substituted, and where the fidelity report appears ===== AGENTS.md ===== # Agent instructions · Decktopus indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, local first, no account, Every position and size stored in English Metric Units on a fixed slide canvas, Measured with the real font before it is accepted into a box, never clipped silently, An outline first, then slots with declared character capacity that the model fills, A presentation file written directly, plus PDF, with a report of anything approximated. 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 - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ===== BUILD_PLAN.md ===== # Build plan · Decktopus A presentation editor whose hard problems are that a slide is a fixed box with no reflow, that generated text has to be measured against that box before it is accepted, and that an export has to reproduce the geometry in the units the target format actually uses. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A slide is a fixed box in real units Everything on a slide has a position and size in the units the presentation format uses. ### Steps 1. Store geometry in the format's units Converting at export is where a deck stops matching itself. Files: `src/model/units.ts`, `tests/units.test.ts` 2. Convert to pixels only at draw time The screen is a view of the document, not its coordinate system. Files: `src/canvas/render.ts` 3. Define layout slots in the same units So a slot's capacity means something measurable. Files: `src/model/layouts.ts` ### Done when - [ ] All geometry is stored as integers in the format's units - [ ] A move then round trip returns identical values - [ ] Zoom never changes stored values - [ ] Layout slots and generated boxes agree exactly ### Watch out - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. ## Phase 2 · Text that fits, or a slide that admits it does not Overflow is a state the editor shows and the export refuses, never something that is quietly clipped. ### Steps 1. Measure only after fonts report ready Otherwise you measured a font nobody will see. Files: `src/text/measure.ts`, `tests/measure.test.ts` 2. Set a shrink floor and mark overflow beyond it Six point body text is not a solution. Files: `src/text/autofit.ts` 3. Surface the overflow count where it cannot be missed In the sorter and the header, not in a console warning. Files: `src/ui/Header.tsx` ### Done when - [ ] No measurement happens before fonts are ready - [ ] Overflow past the shrink floor is marked and counted - [ ] A theme change re-measures the whole deck - [ ] No path clips text without marking overflow ### Watch out - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. ## Phase 3 · Generation that fills a structure A model writes into slots with declared capacity, and anything that does not fit is rejected before it reaches a slide. ### Steps 1. Outline first, slides second The structure is the part a human should own. Files: `src/generate/outline.ts` 2. Give each slot its measured capacity A character budget is the only instruction that actually constrains length. Files: `src/generate/slots.ts`, `tests/generate.test.ts` 3. Reject rather than paste an overflowing response An empty slot is honest; a clipped one is not. Files: `src/generate/apply.ts` ### Done when - [ ] Regenerating a slide leaves the outline unchanged - [ ] Overflowing responses are retried once then rejected - [ ] Rejected slots show their reason - [ ] Model and prompt version are stored per generated slide ### Watch out - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. ## Phase 4 · Presenting it in a room The deck can be delivered from a laptop with notes, a clock and no surprises. ### Steps 1. Preload everything before starting Nothing about the room is under your control. Files: `src/present/preload.ts` 2. Put notes and the next slide on the second display It is the only reason presenter view exists. Files: `src/present/PresenterView.tsx` 3. Document the keys on an overlay You will forget them between decks. Files: `src/present/Help.tsx` ### Done when - [ ] Presenting survives losing the network - [ ] Presenter view shows notes, next slide and timer - [ ] Assets preload with visible progress - [ ] All navigation keys work and are documented ### Watch out - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ## Not in this build - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. ## After v1, if you want it - Speaker note generation from the outline, which is the one generated text with no box to overflow - A rehearsal timer that records how long each slide actually took, which is more useful than any template - Image placement from a local library with alt text required, so the export is accessible by default ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Slide width in the format's units. Confirm against a file your target application made. VITE_SLIDE_W_EMU=12192000 # Required. Slide height in the same units. VITE_SLIDE_H_EMU=6858000 # Required. The shrink floor. Below this, a box is marked as overflowing instead. VITE_MIN_FONT_PT=14 # Optional. Outline and slot generation. The editor works without it. VITE_MODEL_ENDPOINT=https://api.example.com/v1 # Optional · secret. Only needed for generation. Set a provider-side spend limit. VITE_MODEL_API_KEY=sk-...redacted # Required. The override. Turning it on is recorded in the fidelity report. VITE_EXPORT_ALLOW_OVERFLOW=false
You are building a lean indie version of Decktopus. 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 ===== # Decktopus · indie build A presentation editor whose hard problems are that a slide is a fixed box with no reflow, that generated text has to be measured against that box before it is accepted, and that an export has to reproduce the geometry in the units the target format actually uses. 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 | React with TypeScript on Vite, local first, no account | a deck is a document and editing one should not require a server | | Geometry | Every position and size stored in English Metric Units on a fixed slide canvas | the presentation format measures in them and converting at export is where decks go subtly wrong | | Text | Measured with the real font before it is accepted into a box, never clipped silently | a slide does not reflow, so text either fits or the slide is broken | | Generation | An outline first, then slots with declared character capacity that the model fills | asking for a slide and hoping it fits is how generated decks overflow | | Export | A presentation file written directly, plus PDF, with a report of anything approximated | a deck you cannot open in the tool your audience uses is a picture of a deck | ## 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 development server, the build and the export tests. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Theme fonts you are licensed to embed** · free with open licences - Why: Export fidelity depends on embedding, and a font whose licence forbids it must be substituted deliberately rather than by accident. - Get it: Choose open licensed families and keep the licence files with the project. - Verify: Each theme font has a licence file and an embedding permission noted - [ ] **A mainstream presentation application to check exports in** · free with a viewer - Why: The only meaningful test of an export is opening it in the tool your audience will use. - Get it: Any widely used presentation application, including a free viewer. - Verify: An exported deck opens with boxes at measured positions - [ ] **A model API key for outline and slot generation** (optional) · pay per token; a deck is small change - Why: Generation is the only part that costs money and the only part that leaves the machine. - Get it: A key with a spend limit set in the provider console. - Verify: An outline generates and one slide's slots fill within capacity - [ ] **The slide size you are building for** · free - Why: It sets every layout slot and every capacity figure, and changing it later re-measures the entire deck. - Get it: Pick the aspect ratio and dimensions, confirm them against a file produced by your target application, and write them in the configuration. - Verify: The configured dimensions match a file the target application created ## 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: - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments - large template libraries If one of those is essential to you, that is the reason to keep paying for Decktopus, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Decktopus 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 a deck editor that drafts slides from an outline and refuses to let text overflow the slide it is on to replace Decktopus. 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. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, local first, no account · a deck is a document and editing one should not require a server - Geometry: Every position and size stored in English Metric Units on a fixed slide canvas · the presentation format measures in them and converting at export is where decks go subtly wrong - Text: Measured with the real font before it is accepted into a box, never clipped silently · a slide does not reflow, so text either fits or the slide is broken - Generation: An outline first, then slots with declared character capacity that the model fills · asking for a slide and hoping it fits is how generated decks overflow - Export: A presentation file written directly, plus PDF, with a report of anything approximated · a deck you cannot open in the tool your audience uses is a picture of a deck ### Data model (create this before Phase 1) - decks(id, title, slide_w_emu, slide_h_emu, theme_id, created_at, updated_at) - slides(id, deck_id, position, layout_id, notes, generated_from_outline_item) - boxes(id, slide_id, role, x_emu, y_emu, w_emu, h_emu, z, style_json) · role ties a box to a slot in its layout - content(box_id, kind, text, asset_id, autofit_mode, measured_overflow) · measured_overflow is recomputed on every change and is never allowed to persist as true on a published deck - layouts(id, name, slots_json) · each slot declares its box and a character capacity measured at the theme's sizes - themes(id, name, fonts_json, palette_json, asset_ids_json) · fonts are files stored with the deck - outline(deck_id, position, heading, intent, notes) · the structure the generation fills, editable before any slide exists ### Phase 1 · A slide is a fixed box in real units Build: Fix the slide dimensions per deck and store every geometry value in English Metric Units. Render by converting to screen pixels at draw time, never the other way round. Provide layouts whose slots are defined in the same units. Snapping, alignment and distribution all operate on the stored units so nothing drifts through a round trip. Trap: Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. Done when: Every stored geometry value is an integer in English Metric Units, verified in the schema. Moving a box and then exporting and reimporting returns it to the same stored values, tested for exact equality. Changing the display zoom does not change any stored value, tested. Layout slots are defined in the same units and a slide built from a layout has boxes exactly on the slot geometry. Do not build yet: No text measurement, generation, presenting or export yet. ### Phase 2 · Text that fits, or a slide that admits it does not Build: Measure rendered text against its box using the deck's actual fonts, after those fonts have loaded. Offer shrink to fit with a minimum size below which the text is not shrunk further and the box is marked as overflowing. Show overflow in the editor, in the slide sorter and as a count in the deck header. Recompute on every content, style, box or font change. Trap: Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. Done when: Measurement happens only after the deck's fonts report ready, tested by delaying font loading. A box that cannot fit at the minimum size is marked as overflowing and the count appears in the header, tested. Changing the theme re-measures every box in the deck. No rendering path clips text without the box being marked as overflowing, verified by review. Do not build yet: No generation, presenting or export yet. ### Phase 3 · Generation that fills a structure Build: Generate an editable outline first: headings and intent per slide, no visuals. Then, per slide, ask for structured content against the chosen layout's slots, giving each slot its measured character capacity. Validate the response, measure it, and regenerate that slot once if it overflows. If it still overflows, leave the slot empty and say why rather than pasting it in. Trap: Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. Done when: The outline is editable and regenerating a slide never changes the outline, tested. Every generated slot's text is measured before insertion and an overflowing response is retried once then rejected, tested with a response that is far too long. A rejected slot is visibly empty with its reason shown rather than silently filled. The model identifier and the prompt version are stored with every generated slide. Do not build yet: No presenter view or export yet. ### Phase 4 · Presenting it in a room Build: Full screen presenting with keyboard and clicker navigation, a presenter view on a second screen showing notes, the next slide and elapsed time, and a black or white screen key. Preload the next slide's assets. Make the presenting view work from the local file with no network at all, because the room's connection is not your problem to solve at the time. Trap: Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. Done when: Presenting works with the network disconnected after the deck is open, tested. Presenter view shows notes, the next slide and elapsed time on a second display and stays in sync. Assets are preloaded before presenting starts, with a progress indicator, tested with throttled loading. Keyboard, clicker and blank screen keys all work and are documented on one help overlay. Do not build yet: No export yet. ### Phase 5 · A file other people can open Build: Write the presentation package directly from the stored units, embedding the theme fonts where their licences allow and substituting with a named fallback where they do not. Export PDF from the same geometry. Produce a fidelity report naming every substitution, approximation and unsupported effect. Refuse to export a deck with unresolved overflow without an explicit override. Trap: Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. Done when: An exported presentation opens in a mainstream presentation application with boxes at the same positions, checked by measuring a known slide. Exporting a deck with overflowing boxes requires an explicit override and the override is recorded in the fidelity report. The report names every font substitution and unsupported effect. A PDF export and the presentation export place the same box at the same position on the page, compared numerically. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time co-editing, which is a different application architecture and not something to bolt on later. - Importing other people's presentation files, which is a large compatibility project and will disappoint on the first real deck. - Animations and slide transitions, which do not survive export cleanly and are rarely the reason a talk works. - Embedded forms and viewer analytics, which are what the paid product sells to marketers and are a tracking product rather than an editor. ### README must contain - That geometry is stored in the units the presentation format uses, and the conversion factor with the standard it comes from - That text is measured against its box with the real fonts after they load, and that overflow is a state rather than a clip - That generation fills slots with measured capacities and leaves a slot empty rather than overflowing it - That export is blocked while any box overflows, and how the override is recorded - Which fonts are embedded, which are substituted, and where the fidelity report appears ===== AGENTS.md ===== # Agent instructions · Decktopus indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, local first, no account, Every position and size stored in English Metric Units on a fixed slide canvas, Measured with the real font before it is accepted into a box, never clipped silently, An outline first, then slots with declared character capacity that the model fills, A presentation file written directly, plus PDF, with a report of anything approximated. 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 - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ===== BUILD_PLAN.md ===== # Build plan · Decktopus A presentation editor whose hard problems are that a slide is a fixed box with no reflow, that generated text has to be measured against that box before it is accepted, and that an export has to reproduce the geometry in the units the target format actually uses. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A slide is a fixed box in real units Everything on a slide has a position and size in the units the presentation format uses. ### Steps 1. Store geometry in the format's units Converting at export is where a deck stops matching itself. Files: `src/model/units.ts`, `tests/units.test.ts` 2. Convert to pixels only at draw time The screen is a view of the document, not its coordinate system. Files: `src/canvas/render.ts` 3. Define layout slots in the same units So a slot's capacity means something measurable. Files: `src/model/layouts.ts` ### Done when - [ ] All geometry is stored as integers in the format's units - [ ] A move then round trip returns identical values - [ ] Zoom never changes stored values - [ ] Layout slots and generated boxes agree exactly ### Watch out - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. ## Phase 2 · Text that fits, or a slide that admits it does not Overflow is a state the editor shows and the export refuses, never something that is quietly clipped. ### Steps 1. Measure only after fonts report ready Otherwise you measured a font nobody will see. Files: `src/text/measure.ts`, `tests/measure.test.ts` 2. Set a shrink floor and mark overflow beyond it Six point body text is not a solution. Files: `src/text/autofit.ts` 3. Surface the overflow count where it cannot be missed In the sorter and the header, not in a console warning. Files: `src/ui/Header.tsx` ### Done when - [ ] No measurement happens before fonts are ready - [ ] Overflow past the shrink floor is marked and counted - [ ] A theme change re-measures the whole deck - [ ] No path clips text without marking overflow ### Watch out - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. ## Phase 3 · Generation that fills a structure A model writes into slots with declared capacity, and anything that does not fit is rejected before it reaches a slide. ### Steps 1. Outline first, slides second The structure is the part a human should own. Files: `src/generate/outline.ts` 2. Give each slot its measured capacity A character budget is the only instruction that actually constrains length. Files: `src/generate/slots.ts`, `tests/generate.test.ts` 3. Reject rather than paste an overflowing response An empty slot is honest; a clipped one is not. Files: `src/generate/apply.ts` ### Done when - [ ] Regenerating a slide leaves the outline unchanged - [ ] Overflowing responses are retried once then rejected - [ ] Rejected slots show their reason - [ ] Model and prompt version are stored per generated slide ### Watch out - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. ## Phase 4 · Presenting it in a room The deck can be delivered from a laptop with notes, a clock and no surprises. ### Steps 1. Preload everything before starting Nothing about the room is under your control. Files: `src/present/preload.ts` 2. Put notes and the next slide on the second display It is the only reason presenter view exists. Files: `src/present/PresenterView.tsx` 3. Document the keys on an overlay You will forget them between decks. Files: `src/present/Help.tsx` ### Done when - [ ] Presenting survives losing the network - [ ] Presenter view shows notes, next slide and timer - [ ] Assets preload with visible progress - [ ] All navigation keys work and are documented ### Watch out - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ## Not in this build - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. ## After v1, if you want it - Speaker note generation from the outline, which is the one generated text with no box to overflow - A rehearsal timer that records how long each slide actually took, which is more useful than any template - Image placement from a local library with alt text required, so the export is accessible by default ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Slide width in the format's units. Confirm against a file your target application made. VITE_SLIDE_W_EMU=12192000 # Required. Slide height in the same units. VITE_SLIDE_H_EMU=6858000 # Required. The shrink floor. Below this, a box is marked as overflowing instead. VITE_MIN_FONT_PT=14 # Optional. Outline and slot generation. The editor works without it. VITE_MODEL_ENDPOINT=https://api.example.com/v1 # Optional · secret. Only needed for generation. Set a provider-side spend limit. VITE_MODEL_API_KEY=sk-...redacted # Required. The override. Turning it on is recorded in the fidelity report. VITE_EXPORT_ALLOW_OVERFLOW=false
You are building a production product version of Decktopus. 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 ===== # Decktopus · product brief ## Problem The visible AI presentations loop is buildable, but a credible replacement needs more than the first screen. Decktopus earns its keep through interaction polish, collaboration, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A deck editor where every position is stored in the units the export format uses, no text is ever silently cut off, generated content is measured before it is accepted, and an export tells you exactly what it could not reproduce. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - modern browser - no account required ## Explicit non-goals for v1 - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments - large template libraries ## Success criteria - All geometry is confirmed stored in the format's units as integers - Measurement is confirmed to wait for fonts to report ready - An overflowing generated response is confirmed rejected rather than inserted - Export is confirmed blocked while any box overflows, without an override - An exported file is confirmed to open with matching geometry in a mainstream application ===== BRIEF.md ===== # Build brief · Decktopus 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 a deck editor that drafts slides from an outline and refuses to let text overflow the slide it is on to replace Decktopus. 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. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, local first, no account · a deck is a document and editing one should not require a server - Geometry: Every position and size stored in English Metric Units on a fixed slide canvas · the presentation format measures in them and converting at export is where decks go subtly wrong - Text: Measured with the real font before it is accepted into a box, never clipped silently · a slide does not reflow, so text either fits or the slide is broken - Generation: An outline first, then slots with declared character capacity that the model fills · asking for a slide and hoping it fits is how generated decks overflow - Export: A presentation file written directly, plus PDF, with a report of anything approximated · a deck you cannot open in the tool your audience uses is a picture of a deck ### Data model (create this before Phase 1) - decks(id, title, slide_w_emu, slide_h_emu, theme_id, created_at, updated_at) - slides(id, deck_id, position, layout_id, notes, generated_from_outline_item) - boxes(id, slide_id, role, x_emu, y_emu, w_emu, h_emu, z, style_json) · role ties a box to a slot in its layout - content(box_id, kind, text, asset_id, autofit_mode, measured_overflow) · measured_overflow is recomputed on every change and is never allowed to persist as true on a published deck - layouts(id, name, slots_json) · each slot declares its box and a character capacity measured at the theme's sizes - themes(id, name, fonts_json, palette_json, asset_ids_json) · fonts are files stored with the deck - outline(deck_id, position, heading, intent, notes) · the structure the generation fills, editable before any slide exists ### Phase 1 · A slide is a fixed box in real units Build: Fix the slide dimensions per deck and store every geometry value in English Metric Units. Render by converting to screen pixels at draw time, never the other way round. Provide layouts whose slots are defined in the same units. Snapping, alignment and distribution all operate on the stored units so nothing drifts through a round trip. Trap: Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. Done when: Every stored geometry value is an integer in English Metric Units, verified in the schema. Moving a box and then exporting and reimporting returns it to the same stored values, tested for exact equality. Changing the display zoom does not change any stored value, tested. Layout slots are defined in the same units and a slide built from a layout has boxes exactly on the slot geometry. Do not build yet: No text measurement, generation, presenting or export yet. ### Phase 2 · Text that fits, or a slide that admits it does not Build: Measure rendered text against its box using the deck's actual fonts, after those fonts have loaded. Offer shrink to fit with a minimum size below which the text is not shrunk further and the box is marked as overflowing. Show overflow in the editor, in the slide sorter and as a count in the deck header. Recompute on every content, style, box or font change. Trap: Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. Done when: Measurement happens only after the deck's fonts report ready, tested by delaying font loading. A box that cannot fit at the minimum size is marked as overflowing and the count appears in the header, tested. Changing the theme re-measures every box in the deck. No rendering path clips text without the box being marked as overflowing, verified by review. Do not build yet: No generation, presenting or export yet. ### Phase 3 · Generation that fills a structure Build: Generate an editable outline first: headings and intent per slide, no visuals. Then, per slide, ask for structured content against the chosen layout's slots, giving each slot its measured character capacity. Validate the response, measure it, and regenerate that slot once if it overflows. If it still overflows, leave the slot empty and say why rather than pasting it in. Trap: Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. Done when: The outline is editable and regenerating a slide never changes the outline, tested. Every generated slot's text is measured before insertion and an overflowing response is retried once then rejected, tested with a response that is far too long. A rejected slot is visibly empty with its reason shown rather than silently filled. The model identifier and the prompt version are stored with every generated slide. Do not build yet: No presenter view or export yet. ### Phase 4 · Presenting it in a room Build: Full screen presenting with keyboard and clicker navigation, a presenter view on a second screen showing notes, the next slide and elapsed time, and a black or white screen key. Preload the next slide's assets. Make the presenting view work from the local file with no network at all, because the room's connection is not your problem to solve at the time. Trap: Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. Done when: Presenting works with the network disconnected after the deck is open, tested. Presenter view shows notes, the next slide and elapsed time on a second display and stays in sync. Assets are preloaded before presenting starts, with a progress indicator, tested with throttled loading. Keyboard, clicker and blank screen keys all work and are documented on one help overlay. Do not build yet: No export yet. ### Phase 5 · A file other people can open Build: Write the presentation package directly from the stored units, embedding the theme fonts where their licences allow and substituting with a named fallback where they do not. Export PDF from the same geometry. Produce a fidelity report naming every substitution, approximation and unsupported effect. Refuse to export a deck with unresolved overflow without an explicit override. Trap: Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. Done when: An exported presentation opens in a mainstream presentation application with boxes at the same positions, checked by measuring a known slide. Exporting a deck with overflowing boxes requires an explicit override and the override is recorded in the fidelity report. The report names every font substitution and unsupported effect. A PDF export and the presentation export place the same box at the same position on the page, compared numerically. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time co-editing, which is a different application architecture and not something to bolt on later. - Importing other people's presentation files, which is a large compatibility project and will disappoint on the first real deck. - Animations and slide transitions, which do not survive export cleanly and are rarely the reason a talk works. - Embedded forms and viewer analytics, which are what the paid product sells to marketers and are a tracking product rather than an editor. ### README must contain - That geometry is stored in the units the presentation format uses, and the conversion factor with the standard it comes from - That text is measured against its box with the real fonts after they load, and that overflow is a state rather than a clip - That generation fills slots with measured capacities and leaves a slot empty rather than overflowing it - That export is blocked while any box overflows, and how the override is recorded - Which fonts are embedded, which are substituted, and where the fidelity report appears ===== ARCHITECTURE.md ===== # Architecture · Decktopus ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | React with TypeScript on Vite, local first, no account | a deck is a document and editing one should not require a server | | Geometry | Every position and size stored in English Metric Units on a fixed slide canvas | the presentation format measures in them and converting at export is where decks go subtly wrong | | Text | Measured with the real font before it is accepted into a box, never clipped silently | a slide does not reflow, so text either fits or the slide is broken | | Generation | An outline first, then slots with declared character capacity that the model fills | asking for a slide and hoping it fits is how generated decks overflow | | Export | A presentation file written directly, plus PDF, with a report of anything approximated | a deck you cannot open in the tool your audience uses is a picture of a deck | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Document | Slides, boxes and geometry in format units. | Any store, provided pixels never become the coordinate system. | | Text | Measurement, autofit and the overflow state. | Any measurement, provided it waits for fonts and marks overflow. | | Generation | Outline, slot capacities, validation. | Any model, provided an unfittable response is rejected. | | Presenting | Full screen, presenter view, preloading. | Any viewer, provided it works with no network. | | Export | Package writing, fonts, fidelity report. | Any writer, provided the report and the overflow gate survive. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_SLIDE_W_EMU` · required · Slide width in the format's units. Confirm against a file your target application made. - `VITE_SLIDE_H_EMU` · required · Slide height in the same units. - `VITE_MIN_FONT_PT` · required · The shrink floor. Below this, a box is marked as overflowing instead. - `VITE_MODEL_ENDPOINT` · optional · Outline and slot generation. The editor works without it. - `VITE_MODEL_API_KEY` · optional, secret · Only needed for generation. Set a provider-side spend limit. - `VITE_EXPORT_ALLOW_OVERFLOW` · required · The override. Turning it on is recorded in the fidelity report. ## 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 · Decktopus product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React with TypeScript on Vite, local first, no account, Every position and size stored in English Metric Units on a fixed slide canvas, Measured with the real font before it is accepted into a box, never clipped silently, An outline first, then slots with declared character capacity that the model fills, A presentation file written directly, plus PDF, with a report of anything approximated. - 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 - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. - Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. ===== MILESTONES.md ===== # Delivery milestones · Decktopus Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A slide is a fixed box in real units Everything on a slide has a position and size in the units the presentation format uses. ### Steps 1. Store geometry in the format's units Converting at export is where a deck stops matching itself. Files: `src/model/units.ts`, `tests/units.test.ts` 2. Convert to pixels only at draw time The screen is a view of the document, not its coordinate system. Files: `src/canvas/render.ts` 3. Define layout slots in the same units So a slot's capacity means something measurable. Files: `src/model/layouts.ts` ### Done when - [ ] All geometry is stored as integers in the format's units - [ ] A move then round trip returns identical values - [ ] Zoom never changes stored values - [ ] Layout slots and generated boxes agree exactly ### Watch out - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. ## M2 · Text that fits, or a slide that admits it does not Overflow is a state the editor shows and the export refuses, never something that is quietly clipped. ### Steps 1. Measure only after fonts report ready Otherwise you measured a font nobody will see. Files: `src/text/measure.ts`, `tests/measure.test.ts` 2. Set a shrink floor and mark overflow beyond it Six point body text is not a solution. Files: `src/text/autofit.ts` 3. Surface the overflow count where it cannot be missed In the sorter and the header, not in a console warning. Files: `src/ui/Header.tsx` ### Done when - [ ] No measurement happens before fonts are ready - [ ] Overflow past the shrink floor is marked and counted - [ ] A theme change re-measures the whole deck - [ ] No path clips text without marking overflow ### Watch out - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. ## M3 · Generation that fills a structure A model writes into slots with declared capacity, and anything that does not fit is rejected before it reaches a slide. ### Steps 1. Outline first, slides second The structure is the part a human should own. Files: `src/generate/outline.ts` 2. Give each slot its measured capacity A character budget is the only instruction that actually constrains length. Files: `src/generate/slots.ts`, `tests/generate.test.ts` 3. Reject rather than paste an overflowing response An empty slot is honest; a clipped one is not. Files: `src/generate/apply.ts` ### Done when - [ ] Regenerating a slide leaves the outline unchanged - [ ] Overflowing responses are retried once then rejected - [ ] Rejected slots show their reason - [ ] Model and prompt version are stored per generated slide ### Watch out - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. ## M4 · Presenting it in a room The deck can be delivered from a laptop with notes, a clock and no surprises. ### Steps 1. Preload everything before starting Nothing about the room is under your control. Files: `src/present/preload.ts` 2. Put notes and the next slide on the second display It is the only reason presenter view exists. Files: `src/present/PresenterView.tsx` 3. Document the keys on an overlay You will forget them between decks. Files: `src/present/Help.tsx` ### Done when - [ ] Presenting survives losing the network - [ ] Presenter view shows notes, next slide and timer - [ ] Assets preload with visible progress - [ ] All navigation keys work and are documented ### Watch out - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ## M5 · A file other people can open (production only) Export produces a presentation file and a PDF, with a written report of everything that was approximated. ### Steps 1. Write the package from the stored units No conversion means no drift. Files: `src/export/pptx.ts`, `tests/export.test.ts` 2. Report every substitution and approximation Silence about fidelity is how decks embarrass people. Files: `src/export/report.ts` 3. Gate export on the overflow count With a deliberate override, recorded. Files: `src/export/gate.ts` ### Done when - [ ] An exported file opens with matching geometry - [ ] Overflow blocks export without a recorded override - [ ] The fidelity report names every substitution - [ ] PDF and presentation exports agree numerically ### Watch out - Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. ===== OPERATIONS.md ===== # Operations · Decktopus ## Backup Decks live in browser storage, which the browser will delete without asking, so the export is the backup. Keep a scheduled reminder to write every deck out as a package and as JSON, and show the date of the last full export in the interface. ## Restore Import the JSON into a fresh profile and confirm deck and slide counts, then re-measure the whole deck and check that the overflow count is zero where it was zero before. A changed overflow count after a restore means the theme fonts did not come back. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local editor these live in the suite and the interface: the overflow count per deck, generation rejections, font substitutions per export, and the age of the last full export. ## Incident checklist The incident is a talk given from a deck with a sentence cut in half, or a file that would not open on the venue's laptop. The overflow gate handles the first and the export check in a real application handles the second, which is why both are release gates. 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 - [ ] All geometry is confirmed stored in the format's units as integers - [ ] Measurement is confirmed to wait for fonts to report ready - [ ] An overflowing generated response is confirmed rejected rather than inserted - [ ] Export is confirmed blocked while any box overflows, without an override - [ ] An exported file is confirmed to open with matching geometry in a mainstream application ## Launch constraint Do not market omitted Decktopus 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. Slide width in the format's units. Confirm against a file your target application made. VITE_SLIDE_W_EMU=12192000 # Required. Slide height in the same units. VITE_SLIDE_H_EMU=6858000 # Required. The shrink floor. Below this, a box is marked as overflowing instead. VITE_MIN_FONT_PT=14 # Optional. Outline and slot generation. The editor works without it. VITE_MODEL_ENDPOINT=https://api.example.com/v1 # Optional · secret. Only needed for generation. Set a provider-side spend limit. VITE_MODEL_API_KEY=sk-...redacted # Required. The override. Turning it on is recorded in the fidelity report. VITE_EXPORT_ALLOW_OVERFLOW=false
# Decktopus · indie build A presentation editor whose hard problems are that a slide is a fixed box with no reflow, that generated text has to be measured against that box before it is accepted, and that an export has to reproduce the geometry in the units the target format actually uses. 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 | React with TypeScript on Vite, local first, no account | a deck is a document and editing one should not require a server | | Geometry | Every position and size stored in English Metric Units on a fixed slide canvas | the presentation format measures in them and converting at export is where decks go subtly wrong | | Text | Measured with the real font before it is accepted into a box, never clipped silently | a slide does not reflow, so text either fits or the slide is broken | | Generation | An outline first, then slots with declared character capacity that the model fills | asking for a slide and hoping it fits is how generated decks overflow | | Export | A presentation file written directly, plus PDF, with a report of anything approximated | a deck you cannot open in the tool your audience uses is a picture of a deck | ## 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 development server, the build and the export tests. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Theme fonts you are licensed to embed** · free with open licences - Why: Export fidelity depends on embedding, and a font whose licence forbids it must be substituted deliberately rather than by accident. - Get it: Choose open licensed families and keep the licence files with the project. - Verify: Each theme font has a licence file and an embedding permission noted - [ ] **A mainstream presentation application to check exports in** · free with a viewer - Why: The only meaningful test of an export is opening it in the tool your audience will use. - Get it: Any widely used presentation application, including a free viewer. - Verify: An exported deck opens with boxes at measured positions - [ ] **A model API key for outline and slot generation** (optional) · pay per token; a deck is small change - Why: Generation is the only part that costs money and the only part that leaves the machine. - Get it: A key with a spend limit set in the provider console. - Verify: An outline generates and one slide's slots fill within capacity - [ ] **The slide size you are building for** · free - Why: It sets every layout slot and every capacity figure, and changing it later re-measures the entire deck. - Get it: Pick the aspect ratio and dimensions, confirm them against a file produced by your target application, and write them in the configuration. - Verify: The configured dimensions match a file the target application created ## 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: - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments - large template libraries If one of those is essential to you, that is the reason to keep paying for Decktopus, and the README should say so rather than pretend.
# Build brief · Decktopus 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 a deck editor that drafts slides from an outline and refuses to let text overflow the slide it is on to replace Decktopus. 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. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, local first, no account · a deck is a document and editing one should not require a server - Geometry: Every position and size stored in English Metric Units on a fixed slide canvas · the presentation format measures in them and converting at export is where decks go subtly wrong - Text: Measured with the real font before it is accepted into a box, never clipped silently · a slide does not reflow, so text either fits or the slide is broken - Generation: An outline first, then slots with declared character capacity that the model fills · asking for a slide and hoping it fits is how generated decks overflow - Export: A presentation file written directly, plus PDF, with a report of anything approximated · a deck you cannot open in the tool your audience uses is a picture of a deck ### Data model (create this before Phase 1) - decks(id, title, slide_w_emu, slide_h_emu, theme_id, created_at, updated_at) - slides(id, deck_id, position, layout_id, notes, generated_from_outline_item) - boxes(id, slide_id, role, x_emu, y_emu, w_emu, h_emu, z, style_json) · role ties a box to a slot in its layout - content(box_id, kind, text, asset_id, autofit_mode, measured_overflow) · measured_overflow is recomputed on every change and is never allowed to persist as true on a published deck - layouts(id, name, slots_json) · each slot declares its box and a character capacity measured at the theme's sizes - themes(id, name, fonts_json, palette_json, asset_ids_json) · fonts are files stored with the deck - outline(deck_id, position, heading, intent, notes) · the structure the generation fills, editable before any slide exists ### Phase 1 · A slide is a fixed box in real units Build: Fix the slide dimensions per deck and store every geometry value in English Metric Units. Render by converting to screen pixels at draw time, never the other way round. Provide layouts whose slots are defined in the same units. Snapping, alignment and distribution all operate on the stored units so nothing drifts through a round trip. Trap: Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. Done when: Every stored geometry value is an integer in English Metric Units, verified in the schema. Moving a box and then exporting and reimporting returns it to the same stored values, tested for exact equality. Changing the display zoom does not change any stored value, tested. Layout slots are defined in the same units and a slide built from a layout has boxes exactly on the slot geometry. Do not build yet: No text measurement, generation, presenting or export yet. ### Phase 2 · Text that fits, or a slide that admits it does not Build: Measure rendered text against its box using the deck's actual fonts, after those fonts have loaded. Offer shrink to fit with a minimum size below which the text is not shrunk further and the box is marked as overflowing. Show overflow in the editor, in the slide sorter and as a count in the deck header. Recompute on every content, style, box or font change. Trap: Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. Done when: Measurement happens only after the deck's fonts report ready, tested by delaying font loading. A box that cannot fit at the minimum size is marked as overflowing and the count appears in the header, tested. Changing the theme re-measures every box in the deck. No rendering path clips text without the box being marked as overflowing, verified by review. Do not build yet: No generation, presenting or export yet. ### Phase 3 · Generation that fills a structure Build: Generate an editable outline first: headings and intent per slide, no visuals. Then, per slide, ask for structured content against the chosen layout's slots, giving each slot its measured character capacity. Validate the response, measure it, and regenerate that slot once if it overflows. If it still overflows, leave the slot empty and say why rather than pasting it in. Trap: Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. Done when: The outline is editable and regenerating a slide never changes the outline, tested. Every generated slot's text is measured before insertion and an overflowing response is retried once then rejected, tested with a response that is far too long. A rejected slot is visibly empty with its reason shown rather than silently filled. The model identifier and the prompt version are stored with every generated slide. Do not build yet: No presenter view or export yet. ### Phase 4 · Presenting it in a room Build: Full screen presenting with keyboard and clicker navigation, a presenter view on a second screen showing notes, the next slide and elapsed time, and a black or white screen key. Preload the next slide's assets. Make the presenting view work from the local file with no network at all, because the room's connection is not your problem to solve at the time. Trap: Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. Done when: Presenting works with the network disconnected after the deck is open, tested. Presenter view shows notes, the next slide and elapsed time on a second display and stays in sync. Assets are preloaded before presenting starts, with a progress indicator, tested with throttled loading. Keyboard, clicker and blank screen keys all work and are documented on one help overlay. Do not build yet: No export yet. ### Phase 5 · A file other people can open Build: Write the presentation package directly from the stored units, embedding the theme fonts where their licences allow and substituting with a named fallback where they do not. Export PDF from the same geometry. Produce a fidelity report naming every substitution, approximation and unsupported effect. Refuse to export a deck with unresolved overflow without an explicit override. Trap: Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. Done when: An exported presentation opens in a mainstream presentation application with boxes at the same positions, checked by measuring a known slide. Exporting a deck with overflowing boxes requires an explicit override and the override is recorded in the fidelity report. The report names every font substitution and unsupported effect. A PDF export and the presentation export place the same box at the same position on the page, compared numerically. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time co-editing, which is a different application architecture and not something to bolt on later. - Importing other people's presentation files, which is a large compatibility project and will disappoint on the first real deck. - Animations and slide transitions, which do not survive export cleanly and are rarely the reason a talk works. - Embedded forms and viewer analytics, which are what the paid product sells to marketers and are a tracking product rather than an editor. ### README must contain - That geometry is stored in the units the presentation format uses, and the conversion factor with the standard it comes from - That text is measured against its box with the real fonts after they load, and that overflow is a state rather than a clip - That generation fills slots with measured capacities and leaves a slot empty rather than overflowing it - That export is blocked while any box overflows, and how the override is recorded - Which fonts are embedded, which are substituted, and where the fidelity report appears
# Agent instructions · Decktopus indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React with TypeScript on Vite, local first, no account, Every position and size stored in English Metric Units on a fixed slide canvas, Measured with the real font before it is accepted into a box, never clipped silently, An outline first, then slots with declared character capacity that the model fills, A presentation file written directly, plus PDF, with a report of anything approximated. 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 - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator.
# Build plan · Decktopus A presentation editor whose hard problems are that a slide is a fixed box with no reflow, that generated text has to be measured against that box before it is accepted, and that an export has to reproduce the geometry in the units the target format actually uses. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A slide is a fixed box in real units Everything on a slide has a position and size in the units the presentation format uses. ### Steps 1. Store geometry in the format's units Converting at export is where a deck stops matching itself. Files: `src/model/units.ts`, `tests/units.test.ts` 2. Convert to pixels only at draw time The screen is a view of the document, not its coordinate system. Files: `src/canvas/render.ts` 3. Define layout slots in the same units So a slot's capacity means something measurable. Files: `src/model/layouts.ts` ### Done when - [ ] All geometry is stored as integers in the format's units - [ ] A move then round trip returns identical values - [ ] Zoom never changes stored values - [ ] Layout slots and generated boxes agree exactly ### Watch out - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. ## Phase 2 · Text that fits, or a slide that admits it does not Overflow is a state the editor shows and the export refuses, never something that is quietly clipped. ### Steps 1. Measure only after fonts report ready Otherwise you measured a font nobody will see. Files: `src/text/measure.ts`, `tests/measure.test.ts` 2. Set a shrink floor and mark overflow beyond it Six point body text is not a solution. Files: `src/text/autofit.ts` 3. Surface the overflow count where it cannot be missed In the sorter and the header, not in a console warning. Files: `src/ui/Header.tsx` ### Done when - [ ] No measurement happens before fonts are ready - [ ] Overflow past the shrink floor is marked and counted - [ ] A theme change re-measures the whole deck - [ ] No path clips text without marking overflow ### Watch out - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. ## Phase 3 · Generation that fills a structure A model writes into slots with declared capacity, and anything that does not fit is rejected before it reaches a slide. ### Steps 1. Outline first, slides second The structure is the part a human should own. Files: `src/generate/outline.ts` 2. Give each slot its measured capacity A character budget is the only instruction that actually constrains length. Files: `src/generate/slots.ts`, `tests/generate.test.ts` 3. Reject rather than paste an overflowing response An empty slot is honest; a clipped one is not. Files: `src/generate/apply.ts` ### Done when - [ ] Regenerating a slide leaves the outline unchanged - [ ] Overflowing responses are retried once then rejected - [ ] Rejected slots show their reason - [ ] Model and prompt version are stored per generated slide ### Watch out - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. ## Phase 4 · Presenting it in a room The deck can be delivered from a laptop with notes, a clock and no surprises. ### Steps 1. Preload everything before starting Nothing about the room is under your control. Files: `src/present/preload.ts` 2. Put notes and the next slide on the second display It is the only reason presenter view exists. Files: `src/present/PresenterView.tsx` 3. Document the keys on an overlay You will forget them between decks. Files: `src/present/Help.tsx` ### Done when - [ ] Presenting survives losing the network - [ ] Presenter view shows notes, next slide and timer - [ ] Assets preload with visible progress - [ ] All navigation keys work and are documented ### Watch out - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ## Not in this build - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. ## After v1, if you want it - Speaker note generation from the outline, which is the one generated text with no box to overflow - A rehearsal timer that records how long each slide actually took, which is more useful than any template - Image placement from a local library with alt text required, so the export is accessible by default
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Slide width in the format's units. Confirm against a file your target application made. VITE_SLIDE_W_EMU=12192000 # Required. Slide height in the same units. VITE_SLIDE_H_EMU=6858000 # Required. The shrink floor. Below this, a box is marked as overflowing instead. VITE_MIN_FONT_PT=14 # Optional. Outline and slot generation. The editor works without it. VITE_MODEL_ENDPOINT=https://api.example.com/v1 # Optional · secret. Only needed for generation. Set a provider-side spend limit. VITE_MODEL_API_KEY=sk-...redacted # Required. The override. Turning it on is recorded in the fidelity report. VITE_EXPORT_ALLOW_OVERFLOW=false
# Decktopus · product brief ## Problem The visible AI presentations loop is buildable, but a credible replacement needs more than the first screen. Decktopus earns its keep through interaction polish, collaboration, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A deck editor where every position is stored in the units the export format uses, no text is ever silently cut off, generated content is measured before it is accepted, and an export tells you exactly what it could not reproduce. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - modern browser - no account required ## Explicit non-goals for v1 - Real time co-editing. - Importing other people's presentation files. - Animations and transitions. - Embedded forms and viewer analytics. - high-fidelity import and export - presentation and enterprise controls - multiplayer cursors and comments - large template libraries ## Success criteria - All geometry is confirmed stored in the format's units as integers - Measurement is confirmed to wait for fonts to report ready - An overflowing generated response is confirmed rejected rather than inserted - Export is confirmed blocked while any box overflows, without an override - An exported file is confirmed to open with matching geometry in a mainstream application
# Build brief · Decktopus 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 a deck editor that drafts slides from an outline and refuses to let text overflow the slide it is on to replace Decktopus. 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. ### Stack (fixed, do not substitute) - Runtime: React with TypeScript on Vite, local first, no account · a deck is a document and editing one should not require a server - Geometry: Every position and size stored in English Metric Units on a fixed slide canvas · the presentation format measures in them and converting at export is where decks go subtly wrong - Text: Measured with the real font before it is accepted into a box, never clipped silently · a slide does not reflow, so text either fits or the slide is broken - Generation: An outline first, then slots with declared character capacity that the model fills · asking for a slide and hoping it fits is how generated decks overflow - Export: A presentation file written directly, plus PDF, with a report of anything approximated · a deck you cannot open in the tool your audience uses is a picture of a deck ### Data model (create this before Phase 1) - decks(id, title, slide_w_emu, slide_h_emu, theme_id, created_at, updated_at) - slides(id, deck_id, position, layout_id, notes, generated_from_outline_item) - boxes(id, slide_id, role, x_emu, y_emu, w_emu, h_emu, z, style_json) · role ties a box to a slot in its layout - content(box_id, kind, text, asset_id, autofit_mode, measured_overflow) · measured_overflow is recomputed on every change and is never allowed to persist as true on a published deck - layouts(id, name, slots_json) · each slot declares its box and a character capacity measured at the theme's sizes - themes(id, name, fonts_json, palette_json, asset_ids_json) · fonts are files stored with the deck - outline(deck_id, position, heading, intent, notes) · the structure the generation fills, editable before any slide exists ### Phase 1 · A slide is a fixed box in real units Build: Fix the slide dimensions per deck and store every geometry value in English Metric Units. Render by converting to screen pixels at draw time, never the other way round. Provide layouts whose slots are defined in the same units. Snapping, alignment and distribution all operate on the stored units so nothing drifts through a round trip. Trap: Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. Done when: Every stored geometry value is an integer in English Metric Units, verified in the schema. Moving a box and then exporting and reimporting returns it to the same stored values, tested for exact equality. Changing the display zoom does not change any stored value, tested. Layout slots are defined in the same units and a slide built from a layout has boxes exactly on the slot geometry. Do not build yet: No text measurement, generation, presenting or export yet. ### Phase 2 · Text that fits, or a slide that admits it does not Build: Measure rendered text against its box using the deck's actual fonts, after those fonts have loaded. Offer shrink to fit with a minimum size below which the text is not shrunk further and the box is marked as overflowing. Show overflow in the editor, in the slide sorter and as a count in the deck header. Recompute on every content, style, box or font change. Trap: Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. Done when: Measurement happens only after the deck's fonts report ready, tested by delaying font loading. A box that cannot fit at the minimum size is marked as overflowing and the count appears in the header, tested. Changing the theme re-measures every box in the deck. No rendering path clips text without the box being marked as overflowing, verified by review. Do not build yet: No generation, presenting or export yet. ### Phase 3 · Generation that fills a structure Build: Generate an editable outline first: headings and intent per slide, no visuals. Then, per slide, ask for structured content against the chosen layout's slots, giving each slot its measured character capacity. Validate the response, measure it, and regenerate that slot once if it overflows. If it still overflows, leave the slot empty and say why rather than pasting it in. Trap: Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. Done when: The outline is editable and regenerating a slide never changes the outline, tested. Every generated slot's text is measured before insertion and an overflowing response is retried once then rejected, tested with a response that is far too long. A rejected slot is visibly empty with its reason shown rather than silently filled. The model identifier and the prompt version are stored with every generated slide. Do not build yet: No presenter view or export yet. ### Phase 4 · Presenting it in a room Build: Full screen presenting with keyboard and clicker navigation, a presenter view on a second screen showing notes, the next slide and elapsed time, and a black or white screen key. Preload the next slide's assets. Make the presenting view work from the local file with no network at all, because the room's connection is not your problem to solve at the time. Trap: Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. Done when: Presenting works with the network disconnected after the deck is open, tested. Presenter view shows notes, the next slide and elapsed time on a second display and stays in sync. Assets are preloaded before presenting starts, with a progress indicator, tested with throttled loading. Keyboard, clicker and blank screen keys all work and are documented on one help overlay. Do not build yet: No export yet. ### Phase 5 · A file other people can open Build: Write the presentation package directly from the stored units, embedding the theme fonts where their licences allow and substituting with a named fallback where they do not. Export PDF from the same geometry. Produce a fidelity report naming every substitution, approximation and unsupported effect. Refuse to export a deck with unresolved overflow without an explicit override. Trap: Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report. Done when: An exported presentation opens in a mainstream presentation application with boxes at the same positions, checked by measuring a known slide. Exporting a deck with overflowing boxes requires an explicit override and the override is recorded in the fidelity report. The report names every font substitution and unsupported effect. A PDF export and the presentation export place the same box at the same position on the page, compared numerically. Do not build yet: Nothing after this. ### Out of scope (and why) - Real time co-editing, which is a different application architecture and not something to bolt on later. - Importing other people's presentation files, which is a large compatibility project and will disappoint on the first real deck. - Animations and slide transitions, which do not survive export cleanly and are rarely the reason a talk works. - Embedded forms and viewer analytics, which are what the paid product sells to marketers and are a tracking product rather than an editor. ### README must contain - That geometry is stored in the units the presentation format uses, and the conversion factor with the standard it comes from - That text is measured against its box with the real fonts after they load, and that overflow is a state rather than a clip - That generation fills slots with measured capacities and leaves a slot empty rather than overflowing it - That export is blocked while any box overflows, and how the override is recorded - Which fonts are embedded, which are substituted, and where the fidelity report appears
# Architecture · Decktopus ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | React with TypeScript on Vite, local first, no account | a deck is a document and editing one should not require a server | | Geometry | Every position and size stored in English Metric Units on a fixed slide canvas | the presentation format measures in them and converting at export is where decks go subtly wrong | | Text | Measured with the real font before it is accepted into a box, never clipped silently | a slide does not reflow, so text either fits or the slide is broken | | Generation | An outline first, then slots with declared character capacity that the model fills | asking for a slide and hoping it fits is how generated decks overflow | | Export | A presentation file written directly, plus PDF, with a report of anything approximated | a deck you cannot open in the tool your audience uses is a picture of a deck | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Document | Slides, boxes and geometry in format units. | Any store, provided pixels never become the coordinate system. | | Text | Measurement, autofit and the overflow state. | Any measurement, provided it waits for fonts and marks overflow. | | Generation | Outline, slot capacities, validation. | Any model, provided an unfittable response is rejected. | | Presenting | Full screen, presenter view, preloading. | Any viewer, provided it works with no network. | | Export | Package writing, fonts, fidelity report. | Any writer, provided the report and the overflow gate survive. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_SLIDE_W_EMU` · required · Slide width in the format's units. Confirm against a file your target application made. - `VITE_SLIDE_H_EMU` · required · Slide height in the same units. - `VITE_MIN_FONT_PT` · required · The shrink floor. Below this, a box is marked as overflowing instead. - `VITE_MODEL_ENDPOINT` · optional · Outline and slot generation. The editor works without it. - `VITE_MODEL_API_KEY` · optional, secret · Only needed for generation. Set a provider-side spend limit. - `VITE_EXPORT_ALLOW_OVERFLOW` · required · The override. Turning it on is recorded in the fidelity report. ## 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 · Decktopus product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React with TypeScript on Vite, local first, no account, Every position and size stored in English Metric Units on a fixed slide canvas, Measured with the real font before it is accepted into a box, never clipped silently, An outline first, then slots with declared character capacity that the model fills, A presentation file written directly, plus PDF, with a report of anything approximated. - 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 - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. - Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report.
# Delivery milestones · Decktopus Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A slide is a fixed box in real units Everything on a slide has a position and size in the units the presentation format uses. ### Steps 1. Store geometry in the format's units Converting at export is where a deck stops matching itself. Files: `src/model/units.ts`, `tests/units.test.ts` 2. Convert to pixels only at draw time The screen is a view of the document, not its coordinate system. Files: `src/canvas/render.ts` 3. Define layout slots in the same units So a slot's capacity means something measurable. Files: `src/model/layouts.ts` ### Done when - [ ] All geometry is stored as integers in the format's units - [ ] A move then round trip returns identical values - [ ] Zoom never changes stored values - [ ] Layout slots and generated boxes agree exactly ### Watch out - Authoring in CSS pixels and converting only at export means every box is a rounding error away from where it was, and the presentation format measures in English Metric Units where the standard's own example gives a four by three slide as 9144000 by 6858000 for ten by seven and a half inches, which is 914400 units to the inch. Store the units the format uses and convert for the screen, so an export is arithmetic rather than a translation. ## M2 · Text that fits, or a slide that admits it does not Overflow is a state the editor shows and the export refuses, never something that is quietly clipped. ### Steps 1. Measure only after fonts report ready Otherwise you measured a font nobody will see. Files: `src/text/measure.ts`, `tests/measure.test.ts` 2. Set a shrink floor and mark overflow beyond it Six point body text is not a solution. Files: `src/text/autofit.ts` 3. Surface the overflow count where it cannot be missed In the sorter and the header, not in a console warning. Files: `src/ui/Header.tsx` ### Done when - [ ] No measurement happens before fonts are ready - [ ] Overflow past the shrink floor is marked and counted - [ ] A theme change re-measures the whole deck - [ ] No path clips text without marking overflow ### Watch out - Measuring text before the web font has finished loading measures the fallback, so a box that fitted during editing overflows in the export and in the room, and the first time anyone sees it is on a projector. Wait for the font set to report ready before measuring, and re-measure every box when the theme's fonts change. ## M3 · Generation that fills a structure A model writes into slots with declared capacity, and anything that does not fit is rejected before it reaches a slide. ### Steps 1. Outline first, slides second The structure is the part a human should own. Files: `src/generate/outline.ts` 2. Give each slot its measured capacity A character budget is the only instruction that actually constrains length. Files: `src/generate/slots.ts`, `tests/generate.test.ts` 3. Reject rather than paste an overflowing response An empty slot is honest; a clipped one is not. Files: `src/generate/apply.ts` ### Done when - [ ] Regenerating a slide leaves the outline unchanged - [ ] Overflowing responses are retried once then rejected - [ ] Rejected slots show their reason - [ ] Model and prompt version are stored per generated slide ### Watch out - Asking a model for a finished slide and rendering whatever comes back produces a deck where half the bullet lists run off the bottom edge, and because generation feels like magic people ship it without opening slide fourteen. Generate against slots with capacities, measure every returned string, and make an unfittable response an empty slot with a reason attached. ## M4 · Presenting it in a room The deck can be delivered from a laptop with notes, a clock and no surprises. ### Steps 1. Preload everything before starting Nothing about the room is under your control. Files: `src/present/preload.ts` 2. Put notes and the next slide on the second display It is the only reason presenter view exists. Files: `src/present/PresenterView.tsx` 3. Document the keys on an overlay You will forget them between decks. Files: `src/present/Help.tsx` ### Done when - [ ] Presenting survives losing the network - [ ] Presenter view shows notes, next slide and timer - [ ] Assets preload with visible progress - [ ] All navigation keys work and are documented ### Watch out - Presenting from a view that fetches images as each slide appears means a slow room connection turns your deck into a slideshow of grey boxes, in front of people. Preload the whole deck's assets before presenting starts and refuse to begin until they are in memory, with a progress indicator. ## M5 · A file other people can open (production only) Export produces a presentation file and a PDF, with a written report of everything that was approximated. ### Steps 1. Write the package from the stored units No conversion means no drift. Files: `src/export/pptx.ts`, `tests/export.test.ts` 2. Report every substitution and approximation Silence about fidelity is how decks embarrass people. Files: `src/export/report.ts` 3. Gate export on the overflow count With a deliberate override, recorded. Files: `src/export/gate.ts` ### Done when - [ ] An exported file opens with matching geometry - [ ] Overflow blocks export without a recorded override - [ ] The fidelity report names every substitution - [ ] PDF and presentation exports agree numerically ### Watch out - Exporting a deck that still has boxes marked as overflowing produces a file that looks acceptable in your preview and cuts a sentence in half on somebody else's machine, which is the failure this whole build was designed to prevent. Make the overflow count a gate on export, with an override that has to be chosen deliberately and is recorded in the fidelity report.
# Operations · Decktopus ## Backup Decks live in browser storage, which the browser will delete without asking, so the export is the backup. Keep a scheduled reminder to write every deck out as a package and as JSON, and show the date of the last full export in the interface. ## Restore Import the JSON into a fresh profile and confirm deck and slide counts, then re-measure the whole deck and check that the overflow count is zero where it was zero before. A changed overflow count after a restore means the theme fonts did not come back. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local editor these live in the suite and the interface: the overflow count per deck, generation rejections, font substitutions per export, and the age of the last full export. ## Incident checklist The incident is a talk given from a deck with a sentence cut in half, or a file that would not open on the venue's laptop. The overflow gate handles the first and the export check in a real application handles the second, which is why both are release gates. 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 - [ ] All geometry is confirmed stored in the format's units as integers - [ ] Measurement is confirmed to wait for fonts to report ready - [ ] An overflowing generated response is confirmed rejected rather than inserted - [ ] Export is confirmed blocked while any box overflows, without an override - [ ] An exported file is confirmed to open with matching geometry in a mainstream application ## Launch constraint Do not market omitted Decktopus 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. Slide width in the format's units. Confirm against a file your target application made. VITE_SLIDE_W_EMU=12192000 # Required. Slide height in the same units. VITE_SLIDE_H_EMU=6858000 # Required. The shrink floor. Below this, a box is marked as overflowing instead. VITE_MIN_FONT_PT=14 # Optional. Outline and slot generation. The editor works without it. VITE_MODEL_ENDPOINT=https://api.example.com/v1 # Optional · secret. Only needed for generation. Set a provider-side spend limit. VITE_MODEL_API_KEY=sk-...redacted # Required. The override. Turning it on is recorded in the fidelity report. VITE_EXPORT_ALLOW_OVERFLOW=false
$ choose a build depth, inspect the files, then open the complete pack in your agent
Decktopus: The shapes are easy; customers pay for low-friction editing, reliable exports, collaboration, and a canvas that never fights them.
xhigh-fidelity import and export
xpresentation and enterprise controls
xmultiplayer cursors and comments
xlarge template libraries
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Decktopus pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| pro | $24.99/user | $9.99/user | 750 AI credits/month, described as about 150 slides; unlimited storage; export, forms and analytics. |
| business | custom | — | 1,000 AI credits/seat/month; shared workspace, Brand Kit, analytics and custom domain. |
| enterprise | custom | — | Custom seats, governance and support; numeric caps and price are quote-based. |
free tierno permanent free tier verified
billingmonthly + quarterly + annual for Pro; Business cadence and price not exposed; Enterprise quote-based
hidden costsextra AI-credit packs cost extra and do not expire; student discounts can change checkout pricing; the separate API product has its own pricing
verified 2026-08-13 · source ↗
Vibecode Decktopus
Kinda. The core of Decktopus is buildable in a weekend with the prompt on this page, but there are real gaps: high-fidelity import and export, presentation and enterprise controls. Read the honest list above before committing.
How much does Decktopus cost?
Decktopus's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing Decktopus?
Honestly: high-fidelity import and export; presentation and enterprise controls; multiplayer cursors and comments; large template libraries. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Decktopus?
Yes: Presenton (AI-generated, template-based presentations with a drag editor and editable PowerPoint export; forms and hosted engagement analytics are not included.) The prompt is for when you want it exactly your way.