Vibecode Webstudio Cloud
track this build4 phases, 12 steps, beginner friendly0%The visible visual website builder loop is buildable, but a credible replacement needs more than the first screen. Webstudio Cloud earns its keep through editor, CMS, hosting, polish, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of Webstudio Cloud. 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 ===== # Webstudio Cloud · indie build A visual site builder whose hard problems are a document format that survives the editor being rewritten, a publish that is atomic and reversible, and an accessibility gate that runs before the site goes out rather than after somebody complains. 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 | | --- | --- | --- | | Editor | React rendering a node tree, never a contenteditable region over arbitrary markup | an editor that edits HTML directly will produce markup nobody can restyle | | Document | One versioned JSON file per page, on disk, with a migration on load | the site has to be readable the day the editor has a bug | | Styling | Named styles with breakpoints and a token set, overrides allowed but counted | per-element inline declarations are how a builder becomes unmaintainable by month two | | Renderer | A command line renderer that produces static HTML and CSS from the files | if it only renders inside the editor, the editor owns your site | | Publish | Render to a new directory, verify, then swap a pointer, keeping previous releases | a half-written site is worse than an old one | ## 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 editor, the renderer and the publish command. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Somewhere plain to serve static files** · free to a few units of currency a month - Why: The whole argument of this build is that the output is ordinary files, and that is only true once you have served them from something that knows nothing about your builder. - Get it: Any static host, or a small server with a web server in front of the release pointer. - Verify: A published release is reachable and rollback changes what is served - [ ] **Which accessibility criteria the gate enforces** · free - Why: A gate assembled from whatever was easy to check is a gate people switch off, and the list belongs in the README rather than in the implementation. - Get it: Read the current WCAG text, pick the criteria this builder can actually break, and write the list down with levels. - Verify: Each listed criterion has a test and appears in the README - [ ] **A contrast calculation you did not write** · free - Why: Contrast ratio arithmetic is easy to get subtly wrong and a gate that passes failing text is worse than no gate. - Get it: A small maintained library implementing the published ratio formula. - Verify: It reproduces the published worked examples - [ ] **One real page you actually need** · free - Why: Builders designed against imaginary content produce editors that only build the demo, and the missing component always turns up on the second real page. - Get it: Take a page you already have and rebuild it in the editor as the first test of the format. - Verify: The rebuilt page renders and passes the gate ## Quick start ```sh npm run render -- --page home ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. - CMS collaboration and client handoff - managed hosting, forms, and analytics - general-purpose visual editing - large template and component libraries If one of those is essential to you, that is the reason to keep paying for Webstudio Cloud, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Webstudio Cloud 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 visual page builder whose document is a file you can read without the editor, and whose publish step writes a static site you could host anywhere to replace Webstudio Cloud. 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) - Editor: React rendering a node tree, never a contenteditable region over arbitrary markup · an editor that edits HTML directly will produce markup nobody can restyle - Document: One versioned JSON file per page, on disk, with a migration on load · the site has to be readable the day the editor has a bug - Styling: Named styles with breakpoints and a token set, overrides allowed but counted · per-element inline declarations are how a builder becomes unmaintainable by month two - Renderer: A command line renderer that produces static HTML and CSS from the files · if it only renders inside the editor, the editor owns your site - Publish: Render to a new directory, verify, then swap a pointer, keeping previous releases · a half-written site is worse than an old one ### Data model (create this before Phase 1) - projects(id, name, created_at, document_version, token_set_id) · document_version drives the migration on load - pages(id, project_id, path, title, description, doc_path, updated_at, published_hash) - the page document itself: node(id, tag, props, style_refs, children) in a file, which is the source of truth and of which the editor is one view - styles(id, project_id, name, declarations_json, breakpoints_json) · named, reusable, and the default path for every change - assets(id, project_id, filename, bytes, width, height, alt, sha256) · alt is a column rather than an optional property, which is the whole argument - releases(id, project_id, at, dir, page_count, bytes, gate_report_json, rolled_back_at) · the gate report is stored with the release it gated - redirects(id, project_id, from_path, to_path, code) · because renaming a page is the most common way a small site breaks ### Phase 1 · A document that does not need the editor Build: Define the node tree and write each page as a versioned JSON file on disk. Write a renderer that runs from the command line against those files with nothing else running. Migrate older document versions on load, keeping the original file until the migration is saved. Store project metadata in SQLite, but never the documents. Trap: Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. Done when: The command line renderer and the editor preview produce identical HTML for the same page, compared in a test. A document at an older version migrates on load and the original file is retained until the migration is written, tested. Deleting the metadata database and keeping the files reproduces the site, tested. Every page file declares its format version and a file with no version is refused rather than guessed at. Do not build yet: No styling system, publish, gate or export yet. ### Phase 2 · Styling that is still editable in six months Build: Named styles carrying declarations per breakpoint, applied by reference from nodes. A token set for colour, spacing and type, and the renderer emits token values rather than literals. Allow per-element overrides, count them per page, and surface the count in the editor. Trap: Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. Done when: Editing a named style applied to ten elements changes all ten in the rendered output, tested. The rendered CSS contains no colour literal outside the token set, checked by scanning the output in a test. Override counts are computed per page and displayed in the editor. A page above the override threshold blocks publish until acknowledged, tested at the boundary. Do not build yet: No publish, gate, redirects or export yet. ### Phase 3 · A gate that runs before the site goes out Build: Run the document and the rendered output through checks: images with no alt text, text below a contrast ratio, pointer targets under 24 by 24 CSS pixels with the spacing and inline exceptions implemented, a focused control covered by a sticky element, a page with no top-level heading, link text that says nothing. Errors block, warnings do not, and the report is stored on the release. Trap: Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. Done when: A page with an image lacking alt text cannot publish and the error names the asset, tested. A pointer target under the minimum fails unless the spacing exception applies, tested in both directions. A sticky header that covers the focused control fails the focus check, tested with a crafted layout. The gate report is stored on the release and can be read back months later. Do not build yet: No atomic publish, rollback, redirects or export yet. ### Phase 4 · Publishing you can undo Build: Render into a fresh directory, verify every page rendered and every internal link and asset resolves, then swap the pointer the server reads. Keep a configured number of previous releases with their assets. Provide a rollback command. Create a redirect entry whenever a page path changes, and serve it from the published site. Trap: Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. Done when: Killing the process mid-publish leaves the live site serving the previous release unchanged, tested by killing it during the render. Rollback restores the previous release including its assets and the release row records it, tested. Renaming a page path creates a redirect that the published site serves, tested. Every internal link and asset reference in a release resolves, checked before the pointer is swapped and failing the publish if not. Do not build yet: No export, handover mode or byte budget yet. ### Phase 5 · Handing the site to somebody else Build: Export the whole project as a directory of documents, assets, styles and one rendered release, and import it back into an empty installation. Add a content mode that exposes text and images only, with layout locked. Emit script only for components that genuinely need it, and record the transferred byte count per page in the release report. Trap: A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. Done when: Export and reimport reproduces every page byte for byte against the same renderer version, tested. Content mode can change text and swap an image and cannot move, resize or restyle a node, tested by attempting it. A page containing no interactive component ships zero bytes of script, asserted in the release report. The release report records transferred bytes per page and the publish fails above a configured budget. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer editing with cursors and comments, which is a conflict resolution problem rather than an editor problem and is most of what the cloud plan sells. - Managed hosting, domains, forms and analytics, each of which is an operational commitment rather than a weekend of code. - A template marketplace, which is a content business and needs maintenance for every template forever. - Arbitrary code components pasted into pages, which turns the accessibility gate and the byte budget into decoration. ### README must contain - That the site lives in files and how to render it with the editor not running - Which accessibility criteria the publish gate enforces, by name and level, and where the current text is published - That a publish renders to a new directory and swaps a pointer, and the exact rollback command - How page renames become redirects and where those redirects live in the output - What content mode can and cannot change, for the person you hand the site to ===== AGENTS.md ===== # Agent instructions · Webstudio Cloud indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React rendering a node tree, never a contenteditable region over arbitrary markup, One versioned JSON file per page, on disk, with a migration on load, Named styles with breakpoints and a token set, overrides allowed but counted, A command line renderer that produces static HTML and CSS from the files, Render to a new directory, verify, then swap a pointer, keeping previous releases. 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 - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ===== BUILD_PLAN.md ===== # Build plan · Webstudio Cloud A visual site builder whose hard problems are a document format that survives the editor being rewritten, a publish that is atomic and reversible, and an accessibility gate that runs before the site goes out rather than after somebody complains. 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 document that does not need the editor A page is a file with a version, and a command line renderer turns it into HTML. ### Steps 1. Put the document in a file, not a blob The editor should be replaceable without the site being lost. Files: `src/document/schema.ts`, `content/pages/` 2. Write the renderer as a separate command If it only runs inside the editor, the editor owns the site. Files: `src/render/cli.ts`, `tests/render-parity.test.ts` ```sh npm run render -- --page home ``` 3. Migrate on load and keep the original A migration that overwrites the only copy is a gamble. Files: `src/document/migrate.ts`, `tests/migrate.test.ts` ### Done when - [ ] Renderer and editor preview produce identical HTML - [ ] Older documents migrate and the original is retained - [ ] The site rebuilds from files with no database - [ ] An unversioned document is refused ### Watch out - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. ## Phase 2 · Styling that is still editable in six months Changing the heading colour is one edit, not forty. ### Steps 1. Make named styles the default path The override is the exception and should feel like one. Files: `src/styles/apply.ts`, `src/editor/StylePanel.tsx` 2. Emit tokens, not literals Scan the rendered CSS in a test rather than trusting the editor. Files: `src/render/css.ts`, `tests/tokens.test.ts` 3. Count overrides and show the count Drift is invisible until somebody counts it. Files: `src/styles/overrides.ts` ### Done when - [ ] One style edit changes every element using it - [ ] No colour literal escapes the token set - [ ] Override counts are computed and displayed - [ ] A page over the threshold blocks publish ### Watch out - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. ## Phase 3 · A gate that runs before the site goes out Publishing is blocked by checks that name the criteria they enforce. ### Steps 1. Implement criteria by name, with their exceptions A check without the exception is a check people disable. Files: `src/gate/targets.ts`, `src/gate/focus.ts`, `tests/gate.test.ts` 2. Block on errors, warn on the rest A gate that blocks on everything gets bypassed by everyone. Files: `src/gate/run.ts` 3. Store the report on the release So a question about last month has an answer. Files: `src/releases.ts` ### Done when - [ ] A missing alt text blocks publish and names the asset - [ ] Target size passes and fails on the right sides of the exception - [ ] An obscured focus target fails the check - [ ] The gate report is stored and readable later ### Watch out - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. ## Phase 4 · Publishing you can undo A publish either fully happened or did not happen at all, and the previous release is one command away. ### Steps 1. Render to a new directory and verify it Verification before the swap is what makes the swap safe. Files: `src/publish/render.ts`, `src/publish/verify.ts` 2. Swap a pointer, keep the previous releases Rolling back should not require a rebuild. Files: `src/publish/swap.ts`, `tests/publish-crash.test.ts` 3. Write a redirect whenever a path changes Renaming a page is the commonest way a small site breaks. Files: `src/redirects.ts`, `tests/redirects.test.ts` ### Done when - [ ] A crash mid-publish leaves the previous release live - [ ] Rollback restores pages and assets - [ ] A renamed path serves a redirect - [ ] Unresolvable links fail the publish before the swap ### Watch out - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ## Not in this build - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. ## After v1, if you want it - A content collection type, once you have two pages that differ only in their words - A visual diff between the current documents and the live release, which is the review step this build lacks - Automated screenshots per breakpoint stored with each release, so a regression has a before ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Page documents, styles and the token set. This directory is the site. CONTENT_DIR=./content # Required. Images and files, referenced by hash from documents. ASSETS_DIR=./content/assets # Required. One directory per publish. The pointer decides which one is live. RELEASES_DIR=./releases # Required. How many previous releases survive. Enough to roll back twice. RELEASES_KEEP=5 # Required. Project metadata, release history and gate reports. Never the documents. DATABASE_PATH=./data/builder.db # Required. Above this a page blocks publish until acknowledged. MAX_OVERRIDES_PER_PAGE=12 # Required. Transferred bytes per page. Publishing above it fails with the offending pages named. PAGE_BYTE_BUDGET=200000
You are building a lean indie version of Webstudio Cloud. 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 ===== # Webstudio Cloud · indie build A visual site builder whose hard problems are a document format that survives the editor being rewritten, a publish that is atomic and reversible, and an accessibility gate that runs before the site goes out rather than after somebody complains. 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 | | --- | --- | --- | | Editor | React rendering a node tree, never a contenteditable region over arbitrary markup | an editor that edits HTML directly will produce markup nobody can restyle | | Document | One versioned JSON file per page, on disk, with a migration on load | the site has to be readable the day the editor has a bug | | Styling | Named styles with breakpoints and a token set, overrides allowed but counted | per-element inline declarations are how a builder becomes unmaintainable by month two | | Renderer | A command line renderer that produces static HTML and CSS from the files | if it only renders inside the editor, the editor owns your site | | Publish | Render to a new directory, verify, then swap a pointer, keeping previous releases | a half-written site is worse than an old one | ## 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 editor, the renderer and the publish command. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Somewhere plain to serve static files** · free to a few units of currency a month - Why: The whole argument of this build is that the output is ordinary files, and that is only true once you have served them from something that knows nothing about your builder. - Get it: Any static host, or a small server with a web server in front of the release pointer. - Verify: A published release is reachable and rollback changes what is served - [ ] **Which accessibility criteria the gate enforces** · free - Why: A gate assembled from whatever was easy to check is a gate people switch off, and the list belongs in the README rather than in the implementation. - Get it: Read the current WCAG text, pick the criteria this builder can actually break, and write the list down with levels. - Verify: Each listed criterion has a test and appears in the README - [ ] **A contrast calculation you did not write** · free - Why: Contrast ratio arithmetic is easy to get subtly wrong and a gate that passes failing text is worse than no gate. - Get it: A small maintained library implementing the published ratio formula. - Verify: It reproduces the published worked examples - [ ] **One real page you actually need** · free - Why: Builders designed against imaginary content produce editors that only build the demo, and the missing component always turns up on the second real page. - Get it: Take a page you already have and rebuild it in the editor as the first test of the format. - Verify: The rebuilt page renders and passes the gate ## Quick start ```sh npm run render -- --page home ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. - CMS collaboration and client handoff - managed hosting, forms, and analytics - general-purpose visual editing - large template and component libraries If one of those is essential to you, that is the reason to keep paying for Webstudio Cloud, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Webstudio Cloud 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 visual page builder whose document is a file you can read without the editor, and whose publish step writes a static site you could host anywhere to replace Webstudio Cloud. 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) - Editor: React rendering a node tree, never a contenteditable region over arbitrary markup · an editor that edits HTML directly will produce markup nobody can restyle - Document: One versioned JSON file per page, on disk, with a migration on load · the site has to be readable the day the editor has a bug - Styling: Named styles with breakpoints and a token set, overrides allowed but counted · per-element inline declarations are how a builder becomes unmaintainable by month two - Renderer: A command line renderer that produces static HTML and CSS from the files · if it only renders inside the editor, the editor owns your site - Publish: Render to a new directory, verify, then swap a pointer, keeping previous releases · a half-written site is worse than an old one ### Data model (create this before Phase 1) - projects(id, name, created_at, document_version, token_set_id) · document_version drives the migration on load - pages(id, project_id, path, title, description, doc_path, updated_at, published_hash) - the page document itself: node(id, tag, props, style_refs, children) in a file, which is the source of truth and of which the editor is one view - styles(id, project_id, name, declarations_json, breakpoints_json) · named, reusable, and the default path for every change - assets(id, project_id, filename, bytes, width, height, alt, sha256) · alt is a column rather than an optional property, which is the whole argument - releases(id, project_id, at, dir, page_count, bytes, gate_report_json, rolled_back_at) · the gate report is stored with the release it gated - redirects(id, project_id, from_path, to_path, code) · because renaming a page is the most common way a small site breaks ### Phase 1 · A document that does not need the editor Build: Define the node tree and write each page as a versioned JSON file on disk. Write a renderer that runs from the command line against those files with nothing else running. Migrate older document versions on load, keeping the original file until the migration is saved. Store project metadata in SQLite, but never the documents. Trap: Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. Done when: The command line renderer and the editor preview produce identical HTML for the same page, compared in a test. A document at an older version migrates on load and the original file is retained until the migration is written, tested. Deleting the metadata database and keeping the files reproduces the site, tested. Every page file declares its format version and a file with no version is refused rather than guessed at. Do not build yet: No styling system, publish, gate or export yet. ### Phase 2 · Styling that is still editable in six months Build: Named styles carrying declarations per breakpoint, applied by reference from nodes. A token set for colour, spacing and type, and the renderer emits token values rather than literals. Allow per-element overrides, count them per page, and surface the count in the editor. Trap: Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. Done when: Editing a named style applied to ten elements changes all ten in the rendered output, tested. The rendered CSS contains no colour literal outside the token set, checked by scanning the output in a test. Override counts are computed per page and displayed in the editor. A page above the override threshold blocks publish until acknowledged, tested at the boundary. Do not build yet: No publish, gate, redirects or export yet. ### Phase 3 · A gate that runs before the site goes out Build: Run the document and the rendered output through checks: images with no alt text, text below a contrast ratio, pointer targets under 24 by 24 CSS pixels with the spacing and inline exceptions implemented, a focused control covered by a sticky element, a page with no top-level heading, link text that says nothing. Errors block, warnings do not, and the report is stored on the release. Trap: Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. Done when: A page with an image lacking alt text cannot publish and the error names the asset, tested. A pointer target under the minimum fails unless the spacing exception applies, tested in both directions. A sticky header that covers the focused control fails the focus check, tested with a crafted layout. The gate report is stored on the release and can be read back months later. Do not build yet: No atomic publish, rollback, redirects or export yet. ### Phase 4 · Publishing you can undo Build: Render into a fresh directory, verify every page rendered and every internal link and asset resolves, then swap the pointer the server reads. Keep a configured number of previous releases with their assets. Provide a rollback command. Create a redirect entry whenever a page path changes, and serve it from the published site. Trap: Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. Done when: Killing the process mid-publish leaves the live site serving the previous release unchanged, tested by killing it during the render. Rollback restores the previous release including its assets and the release row records it, tested. Renaming a page path creates a redirect that the published site serves, tested. Every internal link and asset reference in a release resolves, checked before the pointer is swapped and failing the publish if not. Do not build yet: No export, handover mode or byte budget yet. ### Phase 5 · Handing the site to somebody else Build: Export the whole project as a directory of documents, assets, styles and one rendered release, and import it back into an empty installation. Add a content mode that exposes text and images only, with layout locked. Emit script only for components that genuinely need it, and record the transferred byte count per page in the release report. Trap: A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. Done when: Export and reimport reproduces every page byte for byte against the same renderer version, tested. Content mode can change text and swap an image and cannot move, resize or restyle a node, tested by attempting it. A page containing no interactive component ships zero bytes of script, asserted in the release report. The release report records transferred bytes per page and the publish fails above a configured budget. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer editing with cursors and comments, which is a conflict resolution problem rather than an editor problem and is most of what the cloud plan sells. - Managed hosting, domains, forms and analytics, each of which is an operational commitment rather than a weekend of code. - A template marketplace, which is a content business and needs maintenance for every template forever. - Arbitrary code components pasted into pages, which turns the accessibility gate and the byte budget into decoration. ### README must contain - That the site lives in files and how to render it with the editor not running - Which accessibility criteria the publish gate enforces, by name and level, and where the current text is published - That a publish renders to a new directory and swaps a pointer, and the exact rollback command - How page renames become redirects and where those redirects live in the output - What content mode can and cannot change, for the person you hand the site to ===== AGENTS.md ===== # Agent instructions · Webstudio Cloud indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React rendering a node tree, never a contenteditable region over arbitrary markup, One versioned JSON file per page, on disk, with a migration on load, Named styles with breakpoints and a token set, overrides allowed but counted, A command line renderer that produces static HTML and CSS from the files, Render to a new directory, verify, then swap a pointer, keeping previous releases. 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 - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ===== BUILD_PLAN.md ===== # Build plan · Webstudio Cloud A visual site builder whose hard problems are a document format that survives the editor being rewritten, a publish that is atomic and reversible, and an accessibility gate that runs before the site goes out rather than after somebody complains. 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 document that does not need the editor A page is a file with a version, and a command line renderer turns it into HTML. ### Steps 1. Put the document in a file, not a blob The editor should be replaceable without the site being lost. Files: `src/document/schema.ts`, `content/pages/` 2. Write the renderer as a separate command If it only runs inside the editor, the editor owns the site. Files: `src/render/cli.ts`, `tests/render-parity.test.ts` ```sh npm run render -- --page home ``` 3. Migrate on load and keep the original A migration that overwrites the only copy is a gamble. Files: `src/document/migrate.ts`, `tests/migrate.test.ts` ### Done when - [ ] Renderer and editor preview produce identical HTML - [ ] Older documents migrate and the original is retained - [ ] The site rebuilds from files with no database - [ ] An unversioned document is refused ### Watch out - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. ## Phase 2 · Styling that is still editable in six months Changing the heading colour is one edit, not forty. ### Steps 1. Make named styles the default path The override is the exception and should feel like one. Files: `src/styles/apply.ts`, `src/editor/StylePanel.tsx` 2. Emit tokens, not literals Scan the rendered CSS in a test rather than trusting the editor. Files: `src/render/css.ts`, `tests/tokens.test.ts` 3. Count overrides and show the count Drift is invisible until somebody counts it. Files: `src/styles/overrides.ts` ### Done when - [ ] One style edit changes every element using it - [ ] No colour literal escapes the token set - [ ] Override counts are computed and displayed - [ ] A page over the threshold blocks publish ### Watch out - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. ## Phase 3 · A gate that runs before the site goes out Publishing is blocked by checks that name the criteria they enforce. ### Steps 1. Implement criteria by name, with their exceptions A check without the exception is a check people disable. Files: `src/gate/targets.ts`, `src/gate/focus.ts`, `tests/gate.test.ts` 2. Block on errors, warn on the rest A gate that blocks on everything gets bypassed by everyone. Files: `src/gate/run.ts` 3. Store the report on the release So a question about last month has an answer. Files: `src/releases.ts` ### Done when - [ ] A missing alt text blocks publish and names the asset - [ ] Target size passes and fails on the right sides of the exception - [ ] An obscured focus target fails the check - [ ] The gate report is stored and readable later ### Watch out - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. ## Phase 4 · Publishing you can undo A publish either fully happened or did not happen at all, and the previous release is one command away. ### Steps 1. Render to a new directory and verify it Verification before the swap is what makes the swap safe. Files: `src/publish/render.ts`, `src/publish/verify.ts` 2. Swap a pointer, keep the previous releases Rolling back should not require a rebuild. Files: `src/publish/swap.ts`, `tests/publish-crash.test.ts` 3. Write a redirect whenever a path changes Renaming a page is the commonest way a small site breaks. Files: `src/redirects.ts`, `tests/redirects.test.ts` ### Done when - [ ] A crash mid-publish leaves the previous release live - [ ] Rollback restores pages and assets - [ ] A renamed path serves a redirect - [ ] Unresolvable links fail the publish before the swap ### Watch out - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ## Not in this build - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. ## After v1, if you want it - A content collection type, once you have two pages that differ only in their words - A visual diff between the current documents and the live release, which is the review step this build lacks - Automated screenshots per breakpoint stored with each release, so a regression has a before ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Page documents, styles and the token set. This directory is the site. CONTENT_DIR=./content # Required. Images and files, referenced by hash from documents. ASSETS_DIR=./content/assets # Required. One directory per publish. The pointer decides which one is live. RELEASES_DIR=./releases # Required. How many previous releases survive. Enough to roll back twice. RELEASES_KEEP=5 # Required. Project metadata, release history and gate reports. Never the documents. DATABASE_PATH=./data/builder.db # Required. Above this a page blocks publish until acknowledged. MAX_OVERRIDES_PER_PAGE=12 # Required. Transferred bytes per page. Publishing above it fails with the offending pages named. PAGE_BYTE_BUDGET=200000
You are building a production product version of Webstudio Cloud. 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 ===== # Webstudio Cloud · product brief ## Problem The visible visual website builder loop is buildable, but a credible replacement needs more than the first screen. Webstudio Cloud earns its keep through editor, CMS, hosting, polish, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A builder whose output is ordinary files, whose publish is atomic and reversible, whose accessibility checks run before the site goes out, and which somebody else can edit the words in without touching the layout. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - deploy target or static hosting - user-owned domain for publishing ## Explicit non-goals for v1 - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. - CMS collaboration and client handoff - managed hosting, forms, and analytics - general-purpose visual editing - large template and component libraries ## Success criteria - Renderer and editor output are confirmed identical - A crash mid-publish is confirmed to leave the previous release live - A missing alt text is confirmed to block the publish - A renamed path is confirmed to serve a redirect - Export and reimport are confirmed to reproduce pages exactly ===== BRIEF.md ===== # Build brief · Webstudio Cloud 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 visual page builder whose document is a file you can read without the editor, and whose publish step writes a static site you could host anywhere to replace Webstudio Cloud. 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) - Editor: React rendering a node tree, never a contenteditable region over arbitrary markup · an editor that edits HTML directly will produce markup nobody can restyle - Document: One versioned JSON file per page, on disk, with a migration on load · the site has to be readable the day the editor has a bug - Styling: Named styles with breakpoints and a token set, overrides allowed but counted · per-element inline declarations are how a builder becomes unmaintainable by month two - Renderer: A command line renderer that produces static HTML and CSS from the files · if it only renders inside the editor, the editor owns your site - Publish: Render to a new directory, verify, then swap a pointer, keeping previous releases · a half-written site is worse than an old one ### Data model (create this before Phase 1) - projects(id, name, created_at, document_version, token_set_id) · document_version drives the migration on load - pages(id, project_id, path, title, description, doc_path, updated_at, published_hash) - the page document itself: node(id, tag, props, style_refs, children) in a file, which is the source of truth and of which the editor is one view - styles(id, project_id, name, declarations_json, breakpoints_json) · named, reusable, and the default path for every change - assets(id, project_id, filename, bytes, width, height, alt, sha256) · alt is a column rather than an optional property, which is the whole argument - releases(id, project_id, at, dir, page_count, bytes, gate_report_json, rolled_back_at) · the gate report is stored with the release it gated - redirects(id, project_id, from_path, to_path, code) · because renaming a page is the most common way a small site breaks ### Phase 1 · A document that does not need the editor Build: Define the node tree and write each page as a versioned JSON file on disk. Write a renderer that runs from the command line against those files with nothing else running. Migrate older document versions on load, keeping the original file until the migration is saved. Store project metadata in SQLite, but never the documents. Trap: Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. Done when: The command line renderer and the editor preview produce identical HTML for the same page, compared in a test. A document at an older version migrates on load and the original file is retained until the migration is written, tested. Deleting the metadata database and keeping the files reproduces the site, tested. Every page file declares its format version and a file with no version is refused rather than guessed at. Do not build yet: No styling system, publish, gate or export yet. ### Phase 2 · Styling that is still editable in six months Build: Named styles carrying declarations per breakpoint, applied by reference from nodes. A token set for colour, spacing and type, and the renderer emits token values rather than literals. Allow per-element overrides, count them per page, and surface the count in the editor. Trap: Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. Done when: Editing a named style applied to ten elements changes all ten in the rendered output, tested. The rendered CSS contains no colour literal outside the token set, checked by scanning the output in a test. Override counts are computed per page and displayed in the editor. A page above the override threshold blocks publish until acknowledged, tested at the boundary. Do not build yet: No publish, gate, redirects or export yet. ### Phase 3 · A gate that runs before the site goes out Build: Run the document and the rendered output through checks: images with no alt text, text below a contrast ratio, pointer targets under 24 by 24 CSS pixels with the spacing and inline exceptions implemented, a focused control covered by a sticky element, a page with no top-level heading, link text that says nothing. Errors block, warnings do not, and the report is stored on the release. Trap: Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. Done when: A page with an image lacking alt text cannot publish and the error names the asset, tested. A pointer target under the minimum fails unless the spacing exception applies, tested in both directions. A sticky header that covers the focused control fails the focus check, tested with a crafted layout. The gate report is stored on the release and can be read back months later. Do not build yet: No atomic publish, rollback, redirects or export yet. ### Phase 4 · Publishing you can undo Build: Render into a fresh directory, verify every page rendered and every internal link and asset resolves, then swap the pointer the server reads. Keep a configured number of previous releases with their assets. Provide a rollback command. Create a redirect entry whenever a page path changes, and serve it from the published site. Trap: Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. Done when: Killing the process mid-publish leaves the live site serving the previous release unchanged, tested by killing it during the render. Rollback restores the previous release including its assets and the release row records it, tested. Renaming a page path creates a redirect that the published site serves, tested. Every internal link and asset reference in a release resolves, checked before the pointer is swapped and failing the publish if not. Do not build yet: No export, handover mode or byte budget yet. ### Phase 5 · Handing the site to somebody else Build: Export the whole project as a directory of documents, assets, styles and one rendered release, and import it back into an empty installation. Add a content mode that exposes text and images only, with layout locked. Emit script only for components that genuinely need it, and record the transferred byte count per page in the release report. Trap: A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. Done when: Export and reimport reproduces every page byte for byte against the same renderer version, tested. Content mode can change text and swap an image and cannot move, resize or restyle a node, tested by attempting it. A page containing no interactive component ships zero bytes of script, asserted in the release report. The release report records transferred bytes per page and the publish fails above a configured budget. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer editing with cursors and comments, which is a conflict resolution problem rather than an editor problem and is most of what the cloud plan sells. - Managed hosting, domains, forms and analytics, each of which is an operational commitment rather than a weekend of code. - A template marketplace, which is a content business and needs maintenance for every template forever. - Arbitrary code components pasted into pages, which turns the accessibility gate and the byte budget into decoration. ### README must contain - That the site lives in files and how to render it with the editor not running - Which accessibility criteria the publish gate enforces, by name and level, and where the current text is published - That a publish renders to a new directory and swaps a pointer, and the exact rollback command - How page renames become redirects and where those redirects live in the output - What content mode can and cannot change, for the person you hand the site to ===== ARCHITECTURE.md ===== # Architecture · Webstudio Cloud ## Stack | Part | Choice | Why | | --- | --- | --- | | Editor | React rendering a node tree, never a contenteditable region over arbitrary markup | an editor that edits HTML directly will produce markup nobody can restyle | | Document | One versioned JSON file per page, on disk, with a migration on load | the site has to be readable the day the editor has a bug | | Styling | Named styles with breakpoints and a token set, overrides allowed but counted | per-element inline declarations are how a builder becomes unmaintainable by month two | | Renderer | A command line renderer that produces static HTML and CSS from the files | if it only renders inside the editor, the editor owns your site | | Publish | Render to a new directory, verify, then swap a pointer, keeping previous releases | a half-written site is worse than an old one | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Document | The node tree, its version and its migrations. | Any format, provided it stays a file with a version. | | Styles | Named styles, tokens and the override count. | Any styling model, provided the named style is the default path. | | Gate | Named criteria, exceptions and the stored report. | Any checker, provided the criteria are named rather than implied. | | Publish | Render, verify, pointer swap, releases and rollback. | Any transport, provided verification happens before the swap. | | Handover | Content mode, export and import. | Any presentation, provided layout stays locked in content mode. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `CONTENT_DIR` · required · Page documents, styles and the token set. This directory is the site. - `ASSETS_DIR` · required · Images and files, referenced by hash from documents. - `RELEASES_DIR` · required · One directory per publish. The pointer decides which one is live. - `RELEASES_KEEP` · required · How many previous releases survive. Enough to roll back twice. - `DATABASE_PATH` · required · Project metadata, release history and gate reports. Never the documents. - `MAX_OVERRIDES_PER_PAGE` · required · Above this a page blocks publish until acknowledged. - `PAGE_BYTE_BUDGET` · required · Transferred bytes per page. Publishing above it fails with the offending pages named. ## 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 · Webstudio Cloud product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React rendering a node tree, never a contenteditable region over arbitrary markup, One versioned JSON file per page, on disk, with a migration on load, Named styles with breakpoints and a token set, overrides allowed but counted, A command line renderer that produces static HTML and CSS from the files, Render to a new directory, verify, then swap a pointer, keeping previous releases. - 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 - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. - A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. ===== MILESTONES.md ===== # Delivery milestones · Webstudio Cloud Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A document that does not need the editor A page is a file with a version, and a command line renderer turns it into HTML. ### Steps 1. Put the document in a file, not a blob The editor should be replaceable without the site being lost. Files: `src/document/schema.ts`, `content/pages/` 2. Write the renderer as a separate command If it only runs inside the editor, the editor owns the site. Files: `src/render/cli.ts`, `tests/render-parity.test.ts` ```sh npm run render -- --page home ``` 3. Migrate on load and keep the original A migration that overwrites the only copy is a gamble. Files: `src/document/migrate.ts`, `tests/migrate.test.ts` ### Done when - [ ] Renderer and editor preview produce identical HTML - [ ] Older documents migrate and the original is retained - [ ] The site rebuilds from files with no database - [ ] An unversioned document is refused ### Watch out - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. ## M2 · Styling that is still editable in six months Changing the heading colour is one edit, not forty. ### Steps 1. Make named styles the default path The override is the exception and should feel like one. Files: `src/styles/apply.ts`, `src/editor/StylePanel.tsx` 2. Emit tokens, not literals Scan the rendered CSS in a test rather than trusting the editor. Files: `src/render/css.ts`, `tests/tokens.test.ts` 3. Count overrides and show the count Drift is invisible until somebody counts it. Files: `src/styles/overrides.ts` ### Done when - [ ] One style edit changes every element using it - [ ] No colour literal escapes the token set - [ ] Override counts are computed and displayed - [ ] A page over the threshold blocks publish ### Watch out - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. ## M3 · A gate that runs before the site goes out Publishing is blocked by checks that name the criteria they enforce. ### Steps 1. Implement criteria by name, with their exceptions A check without the exception is a check people disable. Files: `src/gate/targets.ts`, `src/gate/focus.ts`, `tests/gate.test.ts` 2. Block on errors, warn on the rest A gate that blocks on everything gets bypassed by everyone. Files: `src/gate/run.ts` 3. Store the report on the release So a question about last month has an answer. Files: `src/releases.ts` ### Done when - [ ] A missing alt text blocks publish and names the asset - [ ] Target size passes and fails on the right sides of the exception - [ ] An obscured focus target fails the check - [ ] The gate report is stored and readable later ### Watch out - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. ## M4 · Publishing you can undo A publish either fully happened or did not happen at all, and the previous release is one command away. ### Steps 1. Render to a new directory and verify it Verification before the swap is what makes the swap safe. Files: `src/publish/render.ts`, `src/publish/verify.ts` 2. Swap a pointer, keep the previous releases Rolling back should not require a rebuild. Files: `src/publish/swap.ts`, `tests/publish-crash.test.ts` 3. Write a redirect whenever a path changes Renaming a page is the commonest way a small site breaks. Files: `src/redirects.ts`, `tests/redirects.test.ts` ### Done when - [ ] A crash mid-publish leaves the previous release live - [ ] Rollback restores pages and assets - [ ] A renamed path serves a redirect - [ ] Unresolvable links fail the publish before the swap ### Watch out - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ## M5 · Handing the site to somebody else (production only) The project exports as plain files, someone can edit the words without touching the layout, and pages that need no script ship none. ### Steps 1. Make export and import a round trip you test Anything less is a backup nobody has restored. Files: `src/export.ts`, `tests/roundtrip.test.ts` 2. Add a content mode with layout locked The handover is the moment a builder either works or does not. Files: `src/editor/ContentMode.tsx` 3. Emit script per component and budget the bytes A static page should be a static page. Files: `src/render/script.ts`, `src/publish/budget.ts` ### Done when - [ ] Export and reimport reproduce pages exactly - [ ] Content mode cannot change layout - [ ] A page with no interactive component ships no script - [ ] The byte budget fails a publish that exceeds it ### Watch out - A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. ===== OPERATIONS.md ===== # Operations · Webstudio Cloud ## Backup The content directory is the site and belongs in version control, which also gives you the history for free. Back up the releases directory separately if you want to roll back to something older than the retention, and the database last, since it holds history rather than content. ## Restore Restore the content directory, run the renderer, and compare against the live release hash. A restore is correct when a fresh render of the restored files matches what is currently being served. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Gate failures per publish, override counts trending upward per page, transferred bytes per page against the budget, and whether any published page is serving a redirect loop. Rising override counts are the early warning that the style system has stopped being used. ## Incident checklist The incident is a publish that broke the live site. The pointer swap means the previous release is intact on disk, so the first action is rollback and the second is reading the stored gate report for the release that went out. 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 - [ ] Renderer and editor output are confirmed identical - [ ] A crash mid-publish is confirmed to leave the previous release live - [ ] A missing alt text is confirmed to block the publish - [ ] A renamed path is confirmed to serve a redirect - [ ] Export and reimport are confirmed to reproduce pages exactly ## Launch constraint Do not market omitted Webstudio Cloud 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. Page documents, styles and the token set. This directory is the site. CONTENT_DIR=./content # Required. Images and files, referenced by hash from documents. ASSETS_DIR=./content/assets # Required. One directory per publish. The pointer decides which one is live. RELEASES_DIR=./releases # Required. How many previous releases survive. Enough to roll back twice. RELEASES_KEEP=5 # Required. Project metadata, release history and gate reports. Never the documents. DATABASE_PATH=./data/builder.db # Required. Above this a page blocks publish until acknowledged. MAX_OVERRIDES_PER_PAGE=12 # Required. Transferred bytes per page. Publishing above it fails with the offending pages named. PAGE_BYTE_BUDGET=200000
# Webstudio Cloud · indie build A visual site builder whose hard problems are a document format that survives the editor being rewritten, a publish that is atomic and reversible, and an accessibility gate that runs before the site goes out rather than after somebody complains. 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 | | --- | --- | --- | | Editor | React rendering a node tree, never a contenteditable region over arbitrary markup | an editor that edits HTML directly will produce markup nobody can restyle | | Document | One versioned JSON file per page, on disk, with a migration on load | the site has to be readable the day the editor has a bug | | Styling | Named styles with breakpoints and a token set, overrides allowed but counted | per-element inline declarations are how a builder becomes unmaintainable by month two | | Renderer | A command line renderer that produces static HTML and CSS from the files | if it only renders inside the editor, the editor owns your site | | Publish | Render to a new directory, verify, then swap a pointer, keeping previous releases | a half-written site is worse than an old one | ## 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 editor, the renderer and the publish command. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Somewhere plain to serve static files** · free to a few units of currency a month - Why: The whole argument of this build is that the output is ordinary files, and that is only true once you have served them from something that knows nothing about your builder. - Get it: Any static host, or a small server with a web server in front of the release pointer. - Verify: A published release is reachable and rollback changes what is served - [ ] **Which accessibility criteria the gate enforces** · free - Why: A gate assembled from whatever was easy to check is a gate people switch off, and the list belongs in the README rather than in the implementation. - Get it: Read the current WCAG text, pick the criteria this builder can actually break, and write the list down with levels. - Verify: Each listed criterion has a test and appears in the README - [ ] **A contrast calculation you did not write** · free - Why: Contrast ratio arithmetic is easy to get subtly wrong and a gate that passes failing text is worse than no gate. - Get it: A small maintained library implementing the published ratio formula. - Verify: It reproduces the published worked examples - [ ] **One real page you actually need** · free - Why: Builders designed against imaginary content produce editors that only build the demo, and the missing component always turns up on the second real page. - Get it: Take a page you already have and rebuild it in the editor as the first test of the format. - Verify: The rebuilt page renders and passes the gate ## Quick start ```sh npm run render -- --page home ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. - CMS collaboration and client handoff - managed hosting, forms, and analytics - general-purpose visual editing - large template and component libraries If one of those is essential to you, that is the reason to keep paying for Webstudio Cloud, and the README should say so rather than pretend.
# Build brief · Webstudio Cloud 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 visual page builder whose document is a file you can read without the editor, and whose publish step writes a static site you could host anywhere to replace Webstudio Cloud. 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) - Editor: React rendering a node tree, never a contenteditable region over arbitrary markup · an editor that edits HTML directly will produce markup nobody can restyle - Document: One versioned JSON file per page, on disk, with a migration on load · the site has to be readable the day the editor has a bug - Styling: Named styles with breakpoints and a token set, overrides allowed but counted · per-element inline declarations are how a builder becomes unmaintainable by month two - Renderer: A command line renderer that produces static HTML and CSS from the files · if it only renders inside the editor, the editor owns your site - Publish: Render to a new directory, verify, then swap a pointer, keeping previous releases · a half-written site is worse than an old one ### Data model (create this before Phase 1) - projects(id, name, created_at, document_version, token_set_id) · document_version drives the migration on load - pages(id, project_id, path, title, description, doc_path, updated_at, published_hash) - the page document itself: node(id, tag, props, style_refs, children) in a file, which is the source of truth and of which the editor is one view - styles(id, project_id, name, declarations_json, breakpoints_json) · named, reusable, and the default path for every change - assets(id, project_id, filename, bytes, width, height, alt, sha256) · alt is a column rather than an optional property, which is the whole argument - releases(id, project_id, at, dir, page_count, bytes, gate_report_json, rolled_back_at) · the gate report is stored with the release it gated - redirects(id, project_id, from_path, to_path, code) · because renaming a page is the most common way a small site breaks ### Phase 1 · A document that does not need the editor Build: Define the node tree and write each page as a versioned JSON file on disk. Write a renderer that runs from the command line against those files with nothing else running. Migrate older document versions on load, keeping the original file until the migration is saved. Store project metadata in SQLite, but never the documents. Trap: Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. Done when: The command line renderer and the editor preview produce identical HTML for the same page, compared in a test. A document at an older version migrates on load and the original file is retained until the migration is written, tested. Deleting the metadata database and keeping the files reproduces the site, tested. Every page file declares its format version and a file with no version is refused rather than guessed at. Do not build yet: No styling system, publish, gate or export yet. ### Phase 2 · Styling that is still editable in six months Build: Named styles carrying declarations per breakpoint, applied by reference from nodes. A token set for colour, spacing and type, and the renderer emits token values rather than literals. Allow per-element overrides, count them per page, and surface the count in the editor. Trap: Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. Done when: Editing a named style applied to ten elements changes all ten in the rendered output, tested. The rendered CSS contains no colour literal outside the token set, checked by scanning the output in a test. Override counts are computed per page and displayed in the editor. A page above the override threshold blocks publish until acknowledged, tested at the boundary. Do not build yet: No publish, gate, redirects or export yet. ### Phase 3 · A gate that runs before the site goes out Build: Run the document and the rendered output through checks: images with no alt text, text below a contrast ratio, pointer targets under 24 by 24 CSS pixels with the spacing and inline exceptions implemented, a focused control covered by a sticky element, a page with no top-level heading, link text that says nothing. Errors block, warnings do not, and the report is stored on the release. Trap: Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. Done when: A page with an image lacking alt text cannot publish and the error names the asset, tested. A pointer target under the minimum fails unless the spacing exception applies, tested in both directions. A sticky header that covers the focused control fails the focus check, tested with a crafted layout. The gate report is stored on the release and can be read back months later. Do not build yet: No atomic publish, rollback, redirects or export yet. ### Phase 4 · Publishing you can undo Build: Render into a fresh directory, verify every page rendered and every internal link and asset resolves, then swap the pointer the server reads. Keep a configured number of previous releases with their assets. Provide a rollback command. Create a redirect entry whenever a page path changes, and serve it from the published site. Trap: Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. Done when: Killing the process mid-publish leaves the live site serving the previous release unchanged, tested by killing it during the render. Rollback restores the previous release including its assets and the release row records it, tested. Renaming a page path creates a redirect that the published site serves, tested. Every internal link and asset reference in a release resolves, checked before the pointer is swapped and failing the publish if not. Do not build yet: No export, handover mode or byte budget yet. ### Phase 5 · Handing the site to somebody else Build: Export the whole project as a directory of documents, assets, styles and one rendered release, and import it back into an empty installation. Add a content mode that exposes text and images only, with layout locked. Emit script only for components that genuinely need it, and record the transferred byte count per page in the release report. Trap: A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. Done when: Export and reimport reproduces every page byte for byte against the same renderer version, tested. Content mode can change text and swap an image and cannot move, resize or restyle a node, tested by attempting it. A page containing no interactive component ships zero bytes of script, asserted in the release report. The release report records transferred bytes per page and the publish fails above a configured budget. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer editing with cursors and comments, which is a conflict resolution problem rather than an editor problem and is most of what the cloud plan sells. - Managed hosting, domains, forms and analytics, each of which is an operational commitment rather than a weekend of code. - A template marketplace, which is a content business and needs maintenance for every template forever. - Arbitrary code components pasted into pages, which turns the accessibility gate and the byte budget into decoration. ### README must contain - That the site lives in files and how to render it with the editor not running - Which accessibility criteria the publish gate enforces, by name and level, and where the current text is published - That a publish renders to a new directory and swaps a pointer, and the exact rollback command - How page renames become redirects and where those redirects live in the output - What content mode can and cannot change, for the person you hand the site to
# Agent instructions · Webstudio Cloud indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: React rendering a node tree, never a contenteditable region over arbitrary markup, One versioned JSON file per page, on disk, with a migration on load, Named styles with breakpoints and a token set, overrides allowed but counted, A command line renderer that produces static HTML and CSS from the files, Render to a new directory, verify, then swap a pointer, keeping previous releases. 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 - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once.
# Build plan · Webstudio Cloud A visual site builder whose hard problems are a document format that survives the editor being rewritten, a publish that is atomic and reversible, and an accessibility gate that runs before the site goes out rather than after somebody complains. 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 document that does not need the editor A page is a file with a version, and a command line renderer turns it into HTML. ### Steps 1. Put the document in a file, not a blob The editor should be replaceable without the site being lost. Files: `src/document/schema.ts`, `content/pages/` 2. Write the renderer as a separate command If it only runs inside the editor, the editor owns the site. Files: `src/render/cli.ts`, `tests/render-parity.test.ts` ```sh npm run render -- --page home ``` 3. Migrate on load and keep the original A migration that overwrites the only copy is a gamble. Files: `src/document/migrate.ts`, `tests/migrate.test.ts` ### Done when - [ ] Renderer and editor preview produce identical HTML - [ ] Older documents migrate and the original is retained - [ ] The site rebuilds from files with no database - [ ] An unversioned document is refused ### Watch out - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. ## Phase 2 · Styling that is still editable in six months Changing the heading colour is one edit, not forty. ### Steps 1. Make named styles the default path The override is the exception and should feel like one. Files: `src/styles/apply.ts`, `src/editor/StylePanel.tsx` 2. Emit tokens, not literals Scan the rendered CSS in a test rather than trusting the editor. Files: `src/render/css.ts`, `tests/tokens.test.ts` 3. Count overrides and show the count Drift is invisible until somebody counts it. Files: `src/styles/overrides.ts` ### Done when - [ ] One style edit changes every element using it - [ ] No colour literal escapes the token set - [ ] Override counts are computed and displayed - [ ] A page over the threshold blocks publish ### Watch out - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. ## Phase 3 · A gate that runs before the site goes out Publishing is blocked by checks that name the criteria they enforce. ### Steps 1. Implement criteria by name, with their exceptions A check without the exception is a check people disable. Files: `src/gate/targets.ts`, `src/gate/focus.ts`, `tests/gate.test.ts` 2. Block on errors, warn on the rest A gate that blocks on everything gets bypassed by everyone. Files: `src/gate/run.ts` 3. Store the report on the release So a question about last month has an answer. Files: `src/releases.ts` ### Done when - [ ] A missing alt text blocks publish and names the asset - [ ] Target size passes and fails on the right sides of the exception - [ ] An obscured focus target fails the check - [ ] The gate report is stored and readable later ### Watch out - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. ## Phase 4 · Publishing you can undo A publish either fully happened or did not happen at all, and the previous release is one command away. ### Steps 1. Render to a new directory and verify it Verification before the swap is what makes the swap safe. Files: `src/publish/render.ts`, `src/publish/verify.ts` 2. Swap a pointer, keep the previous releases Rolling back should not require a rebuild. Files: `src/publish/swap.ts`, `tests/publish-crash.test.ts` 3. Write a redirect whenever a path changes Renaming a page is the commonest way a small site breaks. Files: `src/redirects.ts`, `tests/redirects.test.ts` ### Done when - [ ] A crash mid-publish leaves the previous release live - [ ] Rollback restores pages and assets - [ ] A renamed path serves a redirect - [ ] Unresolvable links fail the publish before the swap ### Watch out - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ## Not in this build - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. ## After v1, if you want it - A content collection type, once you have two pages that differ only in their words - A visual diff between the current documents and the live release, which is the review step this build lacks - Automated screenshots per breakpoint stored with each release, so a regression has a before
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Page documents, styles and the token set. This directory is the site. CONTENT_DIR=./content # Required. Images and files, referenced by hash from documents. ASSETS_DIR=./content/assets # Required. One directory per publish. The pointer decides which one is live. RELEASES_DIR=./releases # Required. How many previous releases survive. Enough to roll back twice. RELEASES_KEEP=5 # Required. Project metadata, release history and gate reports. Never the documents. DATABASE_PATH=./data/builder.db # Required. Above this a page blocks publish until acknowledged. MAX_OVERRIDES_PER_PAGE=12 # Required. Transferred bytes per page. Publishing above it fails with the offending pages named. PAGE_BYTE_BUDGET=200000
# Webstudio Cloud · product brief ## Problem The visible visual website builder loop is buildable, but a credible replacement needs more than the first screen. Webstudio Cloud earns its keep through editor, CMS, hosting, polish, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A builder whose output is ordinary files, whose publish is atomic and reversible, whose accessibility checks run before the site goes out, and which somebody else can edit the words in without touching the layout. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - deploy target or static hosting - user-owned domain for publishing ## Explicit non-goals for v1 - Multiplayer editing with cursors and comments. - Managed hosting, domains, forms and analytics. - A template marketplace. - Arbitrary code components pasted into pages. - CMS collaboration and client handoff - managed hosting, forms, and analytics - general-purpose visual editing - large template and component libraries ## Success criteria - Renderer and editor output are confirmed identical - A crash mid-publish is confirmed to leave the previous release live - A missing alt text is confirmed to block the publish - A renamed path is confirmed to serve a redirect - Export and reimport are confirmed to reproduce pages exactly
# Build brief · Webstudio Cloud 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 visual page builder whose document is a file you can read without the editor, and whose publish step writes a static site you could host anywhere to replace Webstudio Cloud. 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) - Editor: React rendering a node tree, never a contenteditable region over arbitrary markup · an editor that edits HTML directly will produce markup nobody can restyle - Document: One versioned JSON file per page, on disk, with a migration on load · the site has to be readable the day the editor has a bug - Styling: Named styles with breakpoints and a token set, overrides allowed but counted · per-element inline declarations are how a builder becomes unmaintainable by month two - Renderer: A command line renderer that produces static HTML and CSS from the files · if it only renders inside the editor, the editor owns your site - Publish: Render to a new directory, verify, then swap a pointer, keeping previous releases · a half-written site is worse than an old one ### Data model (create this before Phase 1) - projects(id, name, created_at, document_version, token_set_id) · document_version drives the migration on load - pages(id, project_id, path, title, description, doc_path, updated_at, published_hash) - the page document itself: node(id, tag, props, style_refs, children) in a file, which is the source of truth and of which the editor is one view - styles(id, project_id, name, declarations_json, breakpoints_json) · named, reusable, and the default path for every change - assets(id, project_id, filename, bytes, width, height, alt, sha256) · alt is a column rather than an optional property, which is the whole argument - releases(id, project_id, at, dir, page_count, bytes, gate_report_json, rolled_back_at) · the gate report is stored with the release it gated - redirects(id, project_id, from_path, to_path, code) · because renaming a page is the most common way a small site breaks ### Phase 1 · A document that does not need the editor Build: Define the node tree and write each page as a versioned JSON file on disk. Write a renderer that runs from the command line against those files with nothing else running. Migrate older document versions on load, keeping the original file until the migration is saved. Store project metadata in SQLite, but never the documents. Trap: Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. Done when: The command line renderer and the editor preview produce identical HTML for the same page, compared in a test. A document at an older version migrates on load and the original file is retained until the migration is written, tested. Deleting the metadata database and keeping the files reproduces the site, tested. Every page file declares its format version and a file with no version is refused rather than guessed at. Do not build yet: No styling system, publish, gate or export yet. ### Phase 2 · Styling that is still editable in six months Build: Named styles carrying declarations per breakpoint, applied by reference from nodes. A token set for colour, spacing and type, and the renderer emits token values rather than literals. Allow per-element overrides, count them per page, and surface the count in the editor. Trap: Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. Done when: Editing a named style applied to ten elements changes all ten in the rendered output, tested. The rendered CSS contains no colour literal outside the token set, checked by scanning the output in a test. Override counts are computed per page and displayed in the editor. A page above the override threshold blocks publish until acknowledged, tested at the boundary. Do not build yet: No publish, gate, redirects or export yet. ### Phase 3 · A gate that runs before the site goes out Build: Run the document and the rendered output through checks: images with no alt text, text below a contrast ratio, pointer targets under 24 by 24 CSS pixels with the spacing and inline exceptions implemented, a focused control covered by a sticky element, a page with no top-level heading, link text that says nothing. Errors block, warnings do not, and the report is stored on the release. Trap: Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. Done when: A page with an image lacking alt text cannot publish and the error names the asset, tested. A pointer target under the minimum fails unless the spacing exception applies, tested in both directions. A sticky header that covers the focused control fails the focus check, tested with a crafted layout. The gate report is stored on the release and can be read back months later. Do not build yet: No atomic publish, rollback, redirects or export yet. ### Phase 4 · Publishing you can undo Build: Render into a fresh directory, verify every page rendered and every internal link and asset resolves, then swap the pointer the server reads. Keep a configured number of previous releases with their assets. Provide a rollback command. Create a redirect entry whenever a page path changes, and serve it from the published site. Trap: Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. Done when: Killing the process mid-publish leaves the live site serving the previous release unchanged, tested by killing it during the render. Rollback restores the previous release including its assets and the release row records it, tested. Renaming a page path creates a redirect that the published site serves, tested. Every internal link and asset reference in a release resolves, checked before the pointer is swapped and failing the publish if not. Do not build yet: No export, handover mode or byte budget yet. ### Phase 5 · Handing the site to somebody else Build: Export the whole project as a directory of documents, assets, styles and one rendered release, and import it back into an empty installation. Add a content mode that exposes text and images only, with layout locked. Emit script only for components that genuinely need it, and record the transferred byte count per page in the release report. Trap: A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed. Done when: Export and reimport reproduces every page byte for byte against the same renderer version, tested. Content mode can change text and swap an image and cannot move, resize or restyle a node, tested by attempting it. A page containing no interactive component ships zero bytes of script, asserted in the release report. The release report records transferred bytes per page and the publish fails above a configured budget. Do not build yet: Nothing after this. ### Out of scope (and why) - Multiplayer editing with cursors and comments, which is a conflict resolution problem rather than an editor problem and is most of what the cloud plan sells. - Managed hosting, domains, forms and analytics, each of which is an operational commitment rather than a weekend of code. - A template marketplace, which is a content business and needs maintenance for every template forever. - Arbitrary code components pasted into pages, which turns the accessibility gate and the byte budget into decoration. ### README must contain - That the site lives in files and how to render it with the editor not running - Which accessibility criteria the publish gate enforces, by name and level, and where the current text is published - That a publish renders to a new directory and swaps a pointer, and the exact rollback command - How page renames become redirects and where those redirects live in the output - What content mode can and cannot change, for the person you hand the site to
# Architecture · Webstudio Cloud ## Stack | Part | Choice | Why | | --- | --- | --- | | Editor | React rendering a node tree, never a contenteditable region over arbitrary markup | an editor that edits HTML directly will produce markup nobody can restyle | | Document | One versioned JSON file per page, on disk, with a migration on load | the site has to be readable the day the editor has a bug | | Styling | Named styles with breakpoints and a token set, overrides allowed but counted | per-element inline declarations are how a builder becomes unmaintainable by month two | | Renderer | A command line renderer that produces static HTML and CSS from the files | if it only renders inside the editor, the editor owns your site | | Publish | Render to a new directory, verify, then swap a pointer, keeping previous releases | a half-written site is worse than an old one | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Document | The node tree, its version and its migrations. | Any format, provided it stays a file with a version. | | Styles | Named styles, tokens and the override count. | Any styling model, provided the named style is the default path. | | Gate | Named criteria, exceptions and the stored report. | Any checker, provided the criteria are named rather than implied. | | Publish | Render, verify, pointer swap, releases and rollback. | Any transport, provided verification happens before the swap. | | Handover | Content mode, export and import. | Any presentation, provided layout stays locked in content mode. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `CONTENT_DIR` · required · Page documents, styles and the token set. This directory is the site. - `ASSETS_DIR` · required · Images and files, referenced by hash from documents. - `RELEASES_DIR` · required · One directory per publish. The pointer decides which one is live. - `RELEASES_KEEP` · required · How many previous releases survive. Enough to roll back twice. - `DATABASE_PATH` · required · Project metadata, release history and gate reports. Never the documents. - `MAX_OVERRIDES_PER_PAGE` · required · Above this a page blocks publish until acknowledged. - `PAGE_BYTE_BUDGET` · required · Transferred bytes per page. Publishing above it fails with the offending pages named. ## 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 · Webstudio Cloud product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: React rendering a node tree, never a contenteditable region over arbitrary markup, One versioned JSON file per page, on disk, with a migration on load, Named styles with breakpoints and a token set, overrides allowed but counted, A command line renderer that produces static HTML and CSS from the files, Render to a new directory, verify, then swap a pointer, keeping previous releases. - 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 - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. - A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed.
# Delivery milestones · Webstudio Cloud Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A document that does not need the editor A page is a file with a version, and a command line renderer turns it into HTML. ### Steps 1. Put the document in a file, not a blob The editor should be replaceable without the site being lost. Files: `src/document/schema.ts`, `content/pages/` 2. Write the renderer as a separate command If it only runs inside the editor, the editor owns the site. Files: `src/render/cli.ts`, `tests/render-parity.test.ts` ```sh npm run render -- --page home ``` 3. Migrate on load and keep the original A migration that overwrites the only copy is a gamble. Files: `src/document/migrate.ts`, `tests/migrate.test.ts` ### Done when - [ ] Renderer and editor preview produce identical HTML - [ ] Older documents migrate and the original is retained - [ ] The site rebuilds from files with no database - [ ] An unversioned document is refused ### Watch out - Keeping the page in a database blob that only the editor can parse means the day the editor has a bug your site is unreachable to you, and this is the reason most abandoned visual builders leave people stranded rather than merely annoyed. Put the document in a file with a version field, write the renderer as a separate command, and treat the editor as one consumer of the format rather than its owner. ## M2 · Styling that is still editable in six months Changing the heading colour is one edit, not forty. ### Steps 1. Make named styles the default path The override is the exception and should feel like one. Files: `src/styles/apply.ts`, `src/editor/StylePanel.tsx` 2. Emit tokens, not literals Scan the rendered CSS in a test rather than trusting the editor. Files: `src/render/css.ts`, `tests/tokens.test.ts` 3. Count overrides and show the count Drift is invisible until somebody counts it. Files: `src/styles/overrides.ts` ### Done when - [ ] One style edit changes every element using it - [ ] No colour literal escapes the token set - [ ] Override counts are computed and displayed - [ ] A page over the threshold blocks publish ### Watch out - Letting every element carry its own declarations is the fastest route to a good-looking first screen and the reason the site becomes unmaintainable, because the tenth heading has drifted from the first and nothing tells you. Make the named style the default path, keep overrides available but counted, and refuse to publish a page above a configured override count until somebody acknowledges it. ## M3 · A gate that runs before the site goes out Publishing is blocked by checks that name the criteria they enforce. ### Steps 1. Implement criteria by name, with their exceptions A check without the exception is a check people disable. Files: `src/gate/targets.ts`, `src/gate/focus.ts`, `tests/gate.test.ts` 2. Block on errors, warn on the rest A gate that blocks on everything gets bypassed by everyone. Files: `src/gate/run.ts` 3. Store the report on the release So a question about last month has an answer. Files: `src/releases.ts` ### Done when - [ ] A missing alt text blocks publish and names the asset - [ ] Target size passes and fails on the right sides of the exception - [ ] An obscured focus target fails the check - [ ] The gate report is stored and readable later ### Watch out - Running an HTML validator and calling it accessibility testing checks something that is explicitly no longer a success criterion, since 4.1.1 Parsing was removed in WCAG 2.2, which reached Recommendation on 12 December 2024 and added the criteria a drag-and-drop builder is most likely to break: 2.5.8 Target Size at 24 by 24 CSS pixels and 2.4.11 Focus Not Obscured. Implement those two by name, including 2.5.8's spacing and inline exceptions, and read the current text before you write the thresholds. ## M4 · Publishing you can undo A publish either fully happened or did not happen at all, and the previous release is one command away. ### Steps 1. Render to a new directory and verify it Verification before the swap is what makes the swap safe. Files: `src/publish/render.ts`, `src/publish/verify.ts` 2. Swap a pointer, keep the previous releases Rolling back should not require a rebuild. Files: `src/publish/swap.ts`, `tests/publish-crash.test.ts` 3. Write a redirect whenever a path changes Renaming a page is the commonest way a small site breaks. Files: `src/redirects.ts`, `tests/redirects.test.ts` ### Done when - [ ] A crash mid-publish leaves the previous release live - [ ] Rollback restores pages and assets - [ ] A renamed path serves a redirect - [ ] Unresolvable links fail the publish before the swap ### Watch out - Rendering over the live directory means a crash halfway leaves a site that is part old and part new, with pages pointing at assets that no longer exist, and the quickest fix under pressure is usually publishing again on top of the mess. Render into a new directory, verify it, swap a pointer, and keep enough previous releases to roll back twice rather than once. ## M5 · Handing the site to somebody else (production only) The project exports as plain files, someone can edit the words without touching the layout, and pages that need no script ship none. ### Steps 1. Make export and import a round trip you test Anything less is a backup nobody has restored. Files: `src/export.ts`, `tests/roundtrip.test.ts` 2. Add a content mode with layout locked The handover is the moment a builder either works or does not. Files: `src/editor/ContentMode.tsx` 3. Emit script per component and budget the bytes A static page should be a static page. Files: `src/render/script.ts`, `src/publish/budget.ts` ### Done when - [ ] Export and reimport reproduce pages exactly - [ ] Content mode cannot change layout - [ ] A page with no interactive component ships no script - [ ] The byte budget fails a publish that exceeds it ### Watch out - A builder that ships its own runtime on every page makes a five paragraph article depend on a framework, and the person you hand the site to then discovers it cannot be hosted on the plain static host they already pay for. Render to HTML and CSS by default, emit script per component rather than per site, and put the byte count per page in the release report so the number is visible rather than assumed.
# Operations · Webstudio Cloud ## Backup The content directory is the site and belongs in version control, which also gives you the history for free. Back up the releases directory separately if you want to roll back to something older than the retention, and the database last, since it holds history rather than content. ## Restore Restore the content directory, run the renderer, and compare against the live release hash. A restore is correct when a fresh render of the restored files matches what is currently being served. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Gate failures per publish, override counts trending upward per page, transferred bytes per page against the budget, and whether any published page is serving a redirect loop. Rising override counts are the early warning that the style system has stopped being used. ## Incident checklist The incident is a publish that broke the live site. The pointer swap means the previous release is intact on disk, so the first action is rollback and the second is reading the stored gate report for the release that went out. 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 - [ ] Renderer and editor output are confirmed identical - [ ] A crash mid-publish is confirmed to leave the previous release live - [ ] A missing alt text is confirmed to block the publish - [ ] A renamed path is confirmed to serve a redirect - [ ] Export and reimport are confirmed to reproduce pages exactly ## Launch constraint Do not market omitted Webstudio Cloud 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. Page documents, styles and the token set. This directory is the site. CONTENT_DIR=./content # Required. Images and files, referenced by hash from documents. ASSETS_DIR=./content/assets # Required. One directory per publish. The pointer decides which one is live. RELEASES_DIR=./releases # Required. How many previous releases survive. Enough to roll back twice. RELEASES_KEEP=5 # Required. Project metadata, release history and gate reports. Never the documents. DATABASE_PATH=./data/builder.db # Required. Above this a page blocks publish until acknowledged. MAX_OVERRIDES_PER_PAGE=12 # Required. Transferred bytes per page. Publishing above it fails with the offending pages named. PAGE_BYTE_BUDGET=200000
$ choose a build depth, inspect the files, then open the complete pack in your agent
Webstudio Cloud: A single site is easy to code. People pay for safe ongoing edits, templates, publishing, domains, collaboration, and no-maintenance hosting.
xCMS collaboration and client handoff
xmanaged hosting, forms, and analytics
xgeneral-purpose visual editing
xlarge template and component libraries
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Webstudio Cloud pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| hobby | $0/workspace | $0/workspace | Unlimited projects, pages and collaborators; 1 wstd.io subdomain; 250 MB assets; 300 form submissions/month; no custom domains or published dynamic CMS |
| pro | — | $15/workspace | Unlimited custom-domain sites, projects, pages and collaborators; 20 GB assets; 100,000 pageviews/month; unlimited forms; backups, staging and CMS |
| team | — | $35/workspace | Pro usage limits plus team workspaces, roles and 5 included seats |
free tierUnlimited draft projects/pages/collaborators, 1 wstd.io subdomain, 250 MB assets and 300 form submissions/month; no custom domain, backups or published dynamic CMS
billingfree + monthly/annual paid plans; annual pricing shown and advertised as saving up to 30%, but monthly amounts were not exposed in accessible markup
hidden costs$20 for each extra 100,000 pageviews after the included 100,000; additional Team seats have an unpublished price; custom-domain registration and external CMS/service charges are separate
verified 2026-08-14 · source ↗
Vibecode Webstudio Cloud
Kinda. The core of Webstudio Cloud is buildable in a weekend with the prompt on this page, but there are real gaps: CMS collaboration and client handoff, managed hosting, forms, and analytics. Read the honest list above before committing.
How much does Webstudio Cloud cost?
Webstudio Cloud'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 Webstudio Cloud?
Honestly: CMS collaboration and client handoff; managed hosting, forms, and analytics; general-purpose visual editing; large template and component libraries. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Webstudio Cloud?
Yes: Webstudio (The same open-source builder on your own server; the subscription disappears and the cloud chores move in.) Plasmic (Hosted visual projects, CMS and custom domains on a permanent free plan; a Plasmic badge replaces the bill.) The prompt is for when you want it exactly your way.