Vibecode Draftbit
track this build4 phases, 12 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Draftbit, compose a React Native app from constrained screens and export the source code. The hard boundary is visual mobile builder, device previews, integrations, collaboration, and build tooling, plus connectors, runtime reliability, and governance.
You are building a lean indie version of Draftbit. 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 ===== # Draftbit · indie build A constrained app builder whose hard problems are an emitter whose output is stable enough to diff, a regeneration path that cannot destroy hand-written code, and a data connector whose credentials never leave your server. 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 | | --- | --- | --- | | Builder runtime | Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose | the builder is a small internal web app and deserves boring infrastructure | | App target | React Native through Expo, emitted as a project you run with the standard tooling | generating into a framework's own conventions means its documentation is also yours | | Vocabulary | A fixed, versioned set of components with typed props, not a freeform canvas | a bounded vocabulary is the only way generated code stays readable | | Emitter | Deterministic output into a scratch tree, then a three-way merge against your working tree | regeneration has to be safe or it will only ever be run once | | Data | A server-side connector proxy holding the credentials, with revocable per-user app tokens | everything bundled into the app is public and the connector key must not be | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Docker and Docker Compose** · free - Why: Runs the builder and its database with one command, which is the difference between a tool you use and a tool you set up once. - Get it: Install Docker Desktop or Docker Engine with the compose plugin. - Verify: docker compose version prints - [ ] **Node.js 22 or newer and the Expo command line tools** · free - Why: Builds the builder and runs the generated project. - Get it: nodejs.org, then the Expo tooling in the generated project. - Verify: A freshly generated project starts and loads on a device - [ ] **A phone with the development client installed** · free - Why: Layouts that look fine in a browser preview are routinely wrong on a real screen, and the awkward-record problem only shows up there. - Get it: Install the development client on your own phone and connect it to the local server. - Verify: A generated screen renders on the phone with the sample data - [ ] **Credentials for one REST data source** · varies - Why: One connector done properly is the whole lesson; the credential is also the thing this build exists to keep off the device. - Get it: An API key for a service you control, with the narrowest permissions that serve the screens you want. - Verify: The proxy returns mapped fields and the key appears nowhere in the generated tree - [ ] **Where generated code ends and hand-written code begins** · free - Why: Every regeneration decision follows from this line, and deciding it after the emitter exists means deciding it badly. - Get it: Write down which directories the generator owns outright and which it must never write to. - Verify: The rule is in the README and enforced by the merge step ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. - visual mobile builder, device previews, integrations, collaboration, and build tooling - hundreds of connectors - pixel-perfect app design - mobile app-store builds - enterprise permissions and scale If one of those is essential to you, that is the reason to keep paying for Draftbit, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Draftbit 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 screen composer that emits readable React Native source and never overwrites what you edited by hand to replace Draftbit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero What this builds gets installed on devices you do not control, and that one fact decides where credentials live. Expo's own documentation is blunt about it: do not store sensitive information such as private keys in the publicly prefixed environment variables, because those variables will be visible in plain text in your compiled application, and when an end user runs your app they have access to all of the code and embedded environment variables in it. That is not specific to one framework. Anything you bundle ships to everyone who installs the app. So the connector's credential never reaches the generated app. The connector runs on your server, the app holds a per-user token you can revoke, and every outbound request is authorised on your side. A builder that lets somebody paste an API key into a data source field and then bundles it has just published that key. Second rule, less dramatic and more likely to kill the project: a generator that overwrites hand edits is a generator nobody runs twice. Decide where the line between generated and hand-written code sits before you write the emitter, and make regenerating into a scratch tree and diffing the ordinary path rather than the recovery procedure. ### Stack (fixed, do not substitute) - Builder runtime: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose · the builder is a small internal web app and deserves boring infrastructure - App target: React Native through Expo, emitted as a project you run with the standard tooling · generating into a framework's own conventions means its documentation is also yours - Vocabulary: A fixed, versioned set of components with typed props, not a freeform canvas · a bounded vocabulary is the only way generated code stays readable - Emitter: Deterministic output into a scratch tree, then a three-way merge against your working tree · regeneration has to be safe or it will only ever be run once - Data: A server-side connector proxy holding the credentials, with revocable per-user app tokens · everything bundled into the app is public and the connector key must not be ### Data model (create this before Phase 1) - projects(id, name, expo_sdk, rn_version, emitter_version, created_at) · the framework versions are recorded because generated code is only valid against the versions it targeted - screens(id, project_id, name, route, tree_json, updated_at) · a screen is a tree of vocabulary components and nothing else - components(name, version, category, props_schema, emitter_ref) · the vocabulary, versioned so a project pinned to an older one still generates - sources(id, project_id, kind, base_url, credential_ref, field_map_json) · credential_ref points into the server's secret store and is never serialised into a screen tree - bindings(id, screen_id, node_path, source_id, field, transform) · which component property reads which field - app_tokens(id, project_id, subject, issued_at, last_used_at, revoked_at) · what the installed app actually holds - generations(id, project_id, at, emitter_version, files_written, files_skipped, conflicts_json) · every regeneration, including what it refused to touch ### Phase 1 · A vocabulary, not a canvas Build: Define perhaps a dozen components with typed property schemas: screen, stack, row, text, image, field, button, list, list item, form. Validate every screen tree against those schemas on save. There is no absolute positioning and no arbitrary style object. Trap: Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. Done when: A screen tree that references an unknown component or an invalid property is refused on save with a message naming the node, tested. No component accepts an arbitrary style object, asserted by scanning the property schemas in the suite. The vocabulary is versioned and a project records which version it targets. A tree round-trips through save and load unchanged, tested. Do not build yet: No bindings, no generation, no connector and no tokens yet. ### Phase 2 · Screens that bind to fields Build: Define a source by its base address and a field map. Bind a component property to a field by path. Preview a screen against a sample response captured from the source, so the layout is judged against real data. A binding to a field that is absent from the map is a validation error. Trap: Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. Done when: A binding to a field not present in the source's field map fails validation with the node path named, tested. The preview renders against a captured sample response rather than static placeholders, verified by changing the sample and seeing the preview change. Every source has at least one sample response stored, enforced before a screen can bind to it. Empty and null field values render a defined state rather than a blank, tested with an awkward record. Do not build yet: No code generation, no merging and no connector proxy yet. ### Phase 3 · An emitter you can diff Build: Emit one file per screen plus a shared components directory. Sort every map before serialising, generate stable component and variable names from node identifiers, and run the output through a formatter as the last step. Record the emitter version in a header comment in every file. Trap: Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. Done when: Generating the same project twice produces byte-identical files, asserted in the suite. Generated source passes the project's own type check and formatter with no changes. Every generated file carries the emitter version and the vocabulary version it was produced against. Renaming a screen changes only that screen's file and the route table, verified by diffing a regeneration. Do not build yet: No merge handling, no connector and no export yet. ### Phase 4 · Regeneration that keeps your edits Build: Generate into a scratch tree. Compare three ways: the last generated output, the current working tree and the new output. Apply files that you have not touched, leave conflicts in place with a clear report, and never write a file whose current contents differ from what was last generated without saying so. Keep the last generated tree so the comparison is possible at all. Trap: Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. Done when: Regenerating over a file edited by hand leaves that file untouched and reports it as a conflict, tested by editing a generated file and regenerating. Files unchanged since the last generation are updated in place. The generations table records files written, files skipped and conflicts for every run. A project with no previous generated tree refuses to regenerate over existing files until one is recorded, tested. Do not build yet: No connector proxy, no tokens and no export yet. ### Phase 5 · The credential stays home Build: Every data request from the app goes to your connector proxy, which holds the source credential server-side, applies the field map and returns only mapped fields. The app carries a per-user token that can be revoked and is checked on every request. Export the whole project: screen trees, generated source, the connector configuration without secrets, and instructions to run it alone. Trap: Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. Done when: No generated file and no bundled environment variable contains a source credential, asserted by a test that scans the emitted tree and the build environment. Revoking an app token causes the next request from that app to fail with a clear status, tested end to end. The proxy returns only fields present in the source's field map, tested by adding an extra field to a stubbed response and checking it is dropped. The exported project builds and runs against the proxy with the builder stopped, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Running arbitrary user-authored code inside the builder, which turns an internal tool into a sandbox problem and a very different security posture. - A broad connector marketplace, because dozens of integrations that keep working is the maintenance the subscription actually pays for. - App store builds and signing, which are credential management, review queues and a release cadence rather than a generator feature. - Pixel-level visual design, since a bounded vocabulary is what keeps the generated code readable and an escape hatch removes the point of generating at all. ### README must contain - That anything bundled into the app is readable by anyone who installs it, quoted from the framework's own documentation, and that this is why the connector runs server-side - That the app holds a revocable per-user token and how to revoke one - That regeneration compares three ways against the last generated tree, and what a reported conflict means - Which framework and vocabulary versions a project targets, and that generated code is only valid against those - How to take the exported project and run it with the builder switched off permanently ===== AGENTS.md ===== # Agent instructions · Draftbit indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose, React Native through Expo, emitted as a project you run with the standard tooling, A fixed, versioned set of components with typed props, not a freeform canvas, Deterministic output into a scratch tree, then a three-way merge against your working tree, A server-side connector proxy holding the credentials, with revocable per-user app tokens. 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 - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ===== BUILD_PLAN.md ===== # Build plan · Draftbit A constrained app builder whose hard problems are an emitter whose output is stable enough to diff, a regeneration path that cannot destroy hand-written code, and a data connector whose credentials never leave your server. 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 vocabulary, not a canvas A screen is a tree of a small number of typed components, and anything the vocabulary cannot express is refused rather than approximated. ### Steps 1. Write the vocabulary as schemas first The schema is the contract between the builder and the emitter. Files: `src/vocabulary/components.ts`, `tests/vocabulary.test.ts` 2. Validate every tree on save An invalid tree reaches the emitter and becomes invalid code. Files: `src/screens/validate.ts` 3. Version the vocabulary and pin projects to it Otherwise adding a component changes every existing project. Files: `src/db/migrations/001_vocabulary.sql` ### Done when - [ ] Unknown components and properties are refused on save - [ ] No component accepts a freeform style object - [ ] The vocabulary is versioned and pinned per project - [ ] Screen trees round-trip unchanged ### Watch out - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. ## Phase 2 · Screens that bind to fields A component property can read a field from a data source, and the builder shows real shapes rather than placeholder text. ### Steps 1. Bind by field path against a declared map A typo in a field name should be a validation error, not an empty screen. Files: `src/bindings/validate.ts`, `tests/bindings.test.ts` 2. Preview against a captured real response Placeholder data flatters every layout. Files: `src/preview/sample.ts` 3. Require an awkward record in every sample Long, empty and null are the three that break layouts. Files: `src/sources/samples.ts` ### Done when - [ ] Unknown field bindings fail validation - [ ] Preview uses a captured sample response - [ ] Every source has a stored sample - [ ] Empty and null values render a defined state ### Watch out - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. ## Phase 3 · An emitter you can diff The same screen tree always produces byte-identical source, formatted and readable. ### Steps 1. Sort everything before you serialise it A diff nobody reads is a generator nobody trusts. Files: `src/emit/serialise.ts`, `tests/deterministic-emit.test.ts` 2. Derive names from stable identifiers Generated names that shuffle are the same bug wearing a hat. Files: `src/emit/names.ts` 3. Stamp the emitter and vocabulary versions into every file So you can tell what produced a file you are staring at in six months. Files: `src/emit/header.ts` ### Done when - [ ] Two generations produce byte-identical output - [ ] Generated code passes type check and formatter - [ ] Every file records emitter and vocabulary versions - [ ] Renaming a screen touches one file and the routes ### Watch out - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. ## Phase 4 · Regeneration that keeps your edits Running the generator over a project you have edited by hand never silently loses a line. ### Steps 1. Keep the last generated tree as the merge base Without it you cannot tell your edit from the generator's. Files: `src/generate/baseline.ts` 2. Compare three ways and refuse to guess Silent overwriting is the failure people never forgive. Files: `src/generate/merge.ts`, `tests/merge.test.ts` 3. Report every skip and conflict, and store it The report is what makes regeneration something you dare to run. Files: `src/generate/report.ts` ### Done when - [ ] A hand-edited file survives regeneration - [ ] Untouched files are updated in place - [ ] Writes, skips and conflicts are recorded per run - [ ] Regeneration without a baseline is refused ### Watch out - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ## Not in this build - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. ## After v1, if you want it - A second data source kind behind the same field map, once the proxy has proved the pattern with one - A vocabulary component that wraps a hand-written file, which is the honest way to add an escape hatch without opening the canvas - A generated test per screen that renders it against the stored sample, which costs little and catches the awkward record ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. The builder's own database. Screens, bindings and generation history. DATABASE_URL=postgres://builder:...@db:5432/builder # Required · secret. Encrypts connector credentials at rest. Losing this means re-entering every source credential. SECRET_STORE_KEY=base64:...redacted # Required. Where the generated app sends data requests. The only address it knows. CONNECTOR_BASE_URL=https://tools.internal.example/connector # Required. How long an app token lives before it must be reissued. APP_TOKEN_TTL_DAYS=30 # Required. Where projects are emitted. The generator owns this tree and nothing above it. GENERATED_ROOT=./generated # Required. The last generated tree, kept as the merge base. Deleting this disables safe regeneration. BASELINE_ROOT=./generated-baseline
You are building a lean indie version of Draftbit. 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 ===== # Draftbit · indie build A constrained app builder whose hard problems are an emitter whose output is stable enough to diff, a regeneration path that cannot destroy hand-written code, and a data connector whose credentials never leave your server. 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 | | --- | --- | --- | | Builder runtime | Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose | the builder is a small internal web app and deserves boring infrastructure | | App target | React Native through Expo, emitted as a project you run with the standard tooling | generating into a framework's own conventions means its documentation is also yours | | Vocabulary | A fixed, versioned set of components with typed props, not a freeform canvas | a bounded vocabulary is the only way generated code stays readable | | Emitter | Deterministic output into a scratch tree, then a three-way merge against your working tree | regeneration has to be safe or it will only ever be run once | | Data | A server-side connector proxy holding the credentials, with revocable per-user app tokens | everything bundled into the app is public and the connector key must not be | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Docker and Docker Compose** · free - Why: Runs the builder and its database with one command, which is the difference between a tool you use and a tool you set up once. - Get it: Install Docker Desktop or Docker Engine with the compose plugin. - Verify: docker compose version prints - [ ] **Node.js 22 or newer and the Expo command line tools** · free - Why: Builds the builder and runs the generated project. - Get it: nodejs.org, then the Expo tooling in the generated project. - Verify: A freshly generated project starts and loads on a device - [ ] **A phone with the development client installed** · free - Why: Layouts that look fine in a browser preview are routinely wrong on a real screen, and the awkward-record problem only shows up there. - Get it: Install the development client on your own phone and connect it to the local server. - Verify: A generated screen renders on the phone with the sample data - [ ] **Credentials for one REST data source** · varies - Why: One connector done properly is the whole lesson; the credential is also the thing this build exists to keep off the device. - Get it: An API key for a service you control, with the narrowest permissions that serve the screens you want. - Verify: The proxy returns mapped fields and the key appears nowhere in the generated tree - [ ] **Where generated code ends and hand-written code begins** · free - Why: Every regeneration decision follows from this line, and deciding it after the emitter exists means deciding it badly. - Get it: Write down which directories the generator owns outright and which it must never write to. - Verify: The rule is in the README and enforced by the merge step ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. - visual mobile builder, device previews, integrations, collaboration, and build tooling - hundreds of connectors - pixel-perfect app design - mobile app-store builds - enterprise permissions and scale If one of those is essential to you, that is the reason to keep paying for Draftbit, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Draftbit 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 screen composer that emits readable React Native source and never overwrites what you edited by hand to replace Draftbit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero What this builds gets installed on devices you do not control, and that one fact decides where credentials live. Expo's own documentation is blunt about it: do not store sensitive information such as private keys in the publicly prefixed environment variables, because those variables will be visible in plain text in your compiled application, and when an end user runs your app they have access to all of the code and embedded environment variables in it. That is not specific to one framework. Anything you bundle ships to everyone who installs the app. So the connector's credential never reaches the generated app. The connector runs on your server, the app holds a per-user token you can revoke, and every outbound request is authorised on your side. A builder that lets somebody paste an API key into a data source field and then bundles it has just published that key. Second rule, less dramatic and more likely to kill the project: a generator that overwrites hand edits is a generator nobody runs twice. Decide where the line between generated and hand-written code sits before you write the emitter, and make regenerating into a scratch tree and diffing the ordinary path rather than the recovery procedure. ### Stack (fixed, do not substitute) - Builder runtime: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose · the builder is a small internal web app and deserves boring infrastructure - App target: React Native through Expo, emitted as a project you run with the standard tooling · generating into a framework's own conventions means its documentation is also yours - Vocabulary: A fixed, versioned set of components with typed props, not a freeform canvas · a bounded vocabulary is the only way generated code stays readable - Emitter: Deterministic output into a scratch tree, then a three-way merge against your working tree · regeneration has to be safe or it will only ever be run once - Data: A server-side connector proxy holding the credentials, with revocable per-user app tokens · everything bundled into the app is public and the connector key must not be ### Data model (create this before Phase 1) - projects(id, name, expo_sdk, rn_version, emitter_version, created_at) · the framework versions are recorded because generated code is only valid against the versions it targeted - screens(id, project_id, name, route, tree_json, updated_at) · a screen is a tree of vocabulary components and nothing else - components(name, version, category, props_schema, emitter_ref) · the vocabulary, versioned so a project pinned to an older one still generates - sources(id, project_id, kind, base_url, credential_ref, field_map_json) · credential_ref points into the server's secret store and is never serialised into a screen tree - bindings(id, screen_id, node_path, source_id, field, transform) · which component property reads which field - app_tokens(id, project_id, subject, issued_at, last_used_at, revoked_at) · what the installed app actually holds - generations(id, project_id, at, emitter_version, files_written, files_skipped, conflicts_json) · every regeneration, including what it refused to touch ### Phase 1 · A vocabulary, not a canvas Build: Define perhaps a dozen components with typed property schemas: screen, stack, row, text, image, field, button, list, list item, form. Validate every screen tree against those schemas on save. There is no absolute positioning and no arbitrary style object. Trap: Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. Done when: A screen tree that references an unknown component or an invalid property is refused on save with a message naming the node, tested. No component accepts an arbitrary style object, asserted by scanning the property schemas in the suite. The vocabulary is versioned and a project records which version it targets. A tree round-trips through save and load unchanged, tested. Do not build yet: No bindings, no generation, no connector and no tokens yet. ### Phase 2 · Screens that bind to fields Build: Define a source by its base address and a field map. Bind a component property to a field by path. Preview a screen against a sample response captured from the source, so the layout is judged against real data. A binding to a field that is absent from the map is a validation error. Trap: Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. Done when: A binding to a field not present in the source's field map fails validation with the node path named, tested. The preview renders against a captured sample response rather than static placeholders, verified by changing the sample and seeing the preview change. Every source has at least one sample response stored, enforced before a screen can bind to it. Empty and null field values render a defined state rather than a blank, tested with an awkward record. Do not build yet: No code generation, no merging and no connector proxy yet. ### Phase 3 · An emitter you can diff Build: Emit one file per screen plus a shared components directory. Sort every map before serialising, generate stable component and variable names from node identifiers, and run the output through a formatter as the last step. Record the emitter version in a header comment in every file. Trap: Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. Done when: Generating the same project twice produces byte-identical files, asserted in the suite. Generated source passes the project's own type check and formatter with no changes. Every generated file carries the emitter version and the vocabulary version it was produced against. Renaming a screen changes only that screen's file and the route table, verified by diffing a regeneration. Do not build yet: No merge handling, no connector and no export yet. ### Phase 4 · Regeneration that keeps your edits Build: Generate into a scratch tree. Compare three ways: the last generated output, the current working tree and the new output. Apply files that you have not touched, leave conflicts in place with a clear report, and never write a file whose current contents differ from what was last generated without saying so. Keep the last generated tree so the comparison is possible at all. Trap: Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. Done when: Regenerating over a file edited by hand leaves that file untouched and reports it as a conflict, tested by editing a generated file and regenerating. Files unchanged since the last generation are updated in place. The generations table records files written, files skipped and conflicts for every run. A project with no previous generated tree refuses to regenerate over existing files until one is recorded, tested. Do not build yet: No connector proxy, no tokens and no export yet. ### Phase 5 · The credential stays home Build: Every data request from the app goes to your connector proxy, which holds the source credential server-side, applies the field map and returns only mapped fields. The app carries a per-user token that can be revoked and is checked on every request. Export the whole project: screen trees, generated source, the connector configuration without secrets, and instructions to run it alone. Trap: Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. Done when: No generated file and no bundled environment variable contains a source credential, asserted by a test that scans the emitted tree and the build environment. Revoking an app token causes the next request from that app to fail with a clear status, tested end to end. The proxy returns only fields present in the source's field map, tested by adding an extra field to a stubbed response and checking it is dropped. The exported project builds and runs against the proxy with the builder stopped, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Running arbitrary user-authored code inside the builder, which turns an internal tool into a sandbox problem and a very different security posture. - A broad connector marketplace, because dozens of integrations that keep working is the maintenance the subscription actually pays for. - App store builds and signing, which are credential management, review queues and a release cadence rather than a generator feature. - Pixel-level visual design, since a bounded vocabulary is what keeps the generated code readable and an escape hatch removes the point of generating at all. ### README must contain - That anything bundled into the app is readable by anyone who installs it, quoted from the framework's own documentation, and that this is why the connector runs server-side - That the app holds a revocable per-user token and how to revoke one - That regeneration compares three ways against the last generated tree, and what a reported conflict means - Which framework and vocabulary versions a project targets, and that generated code is only valid against those - How to take the exported project and run it with the builder switched off permanently ===== AGENTS.md ===== # Agent instructions · Draftbit indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose, React Native through Expo, emitted as a project you run with the standard tooling, A fixed, versioned set of components with typed props, not a freeform canvas, Deterministic output into a scratch tree, then a three-way merge against your working tree, A server-side connector proxy holding the credentials, with revocable per-user app tokens. 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 - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ===== BUILD_PLAN.md ===== # Build plan · Draftbit A constrained app builder whose hard problems are an emitter whose output is stable enough to diff, a regeneration path that cannot destroy hand-written code, and a data connector whose credentials never leave your server. 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 vocabulary, not a canvas A screen is a tree of a small number of typed components, and anything the vocabulary cannot express is refused rather than approximated. ### Steps 1. Write the vocabulary as schemas first The schema is the contract between the builder and the emitter. Files: `src/vocabulary/components.ts`, `tests/vocabulary.test.ts` 2. Validate every tree on save An invalid tree reaches the emitter and becomes invalid code. Files: `src/screens/validate.ts` 3. Version the vocabulary and pin projects to it Otherwise adding a component changes every existing project. Files: `src/db/migrations/001_vocabulary.sql` ### Done when - [ ] Unknown components and properties are refused on save - [ ] No component accepts a freeform style object - [ ] The vocabulary is versioned and pinned per project - [ ] Screen trees round-trip unchanged ### Watch out - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. ## Phase 2 · Screens that bind to fields A component property can read a field from a data source, and the builder shows real shapes rather than placeholder text. ### Steps 1. Bind by field path against a declared map A typo in a field name should be a validation error, not an empty screen. Files: `src/bindings/validate.ts`, `tests/bindings.test.ts` 2. Preview against a captured real response Placeholder data flatters every layout. Files: `src/preview/sample.ts` 3. Require an awkward record in every sample Long, empty and null are the three that break layouts. Files: `src/sources/samples.ts` ### Done when - [ ] Unknown field bindings fail validation - [ ] Preview uses a captured sample response - [ ] Every source has a stored sample - [ ] Empty and null values render a defined state ### Watch out - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. ## Phase 3 · An emitter you can diff The same screen tree always produces byte-identical source, formatted and readable. ### Steps 1. Sort everything before you serialise it A diff nobody reads is a generator nobody trusts. Files: `src/emit/serialise.ts`, `tests/deterministic-emit.test.ts` 2. Derive names from stable identifiers Generated names that shuffle are the same bug wearing a hat. Files: `src/emit/names.ts` 3. Stamp the emitter and vocabulary versions into every file So you can tell what produced a file you are staring at in six months. Files: `src/emit/header.ts` ### Done when - [ ] Two generations produce byte-identical output - [ ] Generated code passes type check and formatter - [ ] Every file records emitter and vocabulary versions - [ ] Renaming a screen touches one file and the routes ### Watch out - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. ## Phase 4 · Regeneration that keeps your edits Running the generator over a project you have edited by hand never silently loses a line. ### Steps 1. Keep the last generated tree as the merge base Without it you cannot tell your edit from the generator's. Files: `src/generate/baseline.ts` 2. Compare three ways and refuse to guess Silent overwriting is the failure people never forgive. Files: `src/generate/merge.ts`, `tests/merge.test.ts` 3. Report every skip and conflict, and store it The report is what makes regeneration something you dare to run. Files: `src/generate/report.ts` ### Done when - [ ] A hand-edited file survives regeneration - [ ] Untouched files are updated in place - [ ] Writes, skips and conflicts are recorded per run - [ ] Regeneration without a baseline is refused ### Watch out - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ## Not in this build - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. ## After v1, if you want it - A second data source kind behind the same field map, once the proxy has proved the pattern with one - A vocabulary component that wraps a hand-written file, which is the honest way to add an escape hatch without opening the canvas - A generated test per screen that renders it against the stored sample, which costs little and catches the awkward record ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. The builder's own database. Screens, bindings and generation history. DATABASE_URL=postgres://builder:...@db:5432/builder # Required · secret. Encrypts connector credentials at rest. Losing this means re-entering every source credential. SECRET_STORE_KEY=base64:...redacted # Required. Where the generated app sends data requests. The only address it knows. CONNECTOR_BASE_URL=https://tools.internal.example/connector # Required. How long an app token lives before it must be reissued. APP_TOKEN_TTL_DAYS=30 # Required. Where projects are emitted. The generator owns this tree and nothing above it. GENERATED_ROOT=./generated # Required. The last generated tree, kept as the merge base. Deleting this disables safe regeneration. BASELINE_ROOT=./generated-baseline
You are building a production product version of Draftbit. 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 ===== # Draftbit · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Draftbit, compose a React Native app from constrained screens and export the source code. The hard boundary is visual mobile builder, device previews, integrations, collaboration, and build tooling, plus connectors, runtime reliability, and governance. ## Product outcome A builder whose generated code is readable and diffable, whose regeneration cannot destroy your edits, and whose apps ship with a revocable token instead of a credential anyone can read out of the bundle. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Docker - PostgreSQL - self-hosted deploy target - API credentials for one selected integration ## Explicit non-goals for v1 - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. - visual mobile builder, device previews, integrations, collaboration, and build tooling - hundreds of connectors - pixel-perfect app design - mobile app-store builds - enterprise permissions and scale ## Success criteria - No credential is confirmed present in generated code or the bundle - Two generations are confirmed byte-identical - A hand-edited file is confirmed to survive regeneration - A revoked app token is confirmed to fail the next request - The exported project is confirmed to run with the builder stopped ===== BRIEF.md ===== # Build brief · Draftbit 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 screen composer that emits readable React Native source and never overwrites what you edited by hand to replace Draftbit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero What this builds gets installed on devices you do not control, and that one fact decides where credentials live. Expo's own documentation is blunt about it: do not store sensitive information such as private keys in the publicly prefixed environment variables, because those variables will be visible in plain text in your compiled application, and when an end user runs your app they have access to all of the code and embedded environment variables in it. That is not specific to one framework. Anything you bundle ships to everyone who installs the app. So the connector's credential never reaches the generated app. The connector runs on your server, the app holds a per-user token you can revoke, and every outbound request is authorised on your side. A builder that lets somebody paste an API key into a data source field and then bundles it has just published that key. Second rule, less dramatic and more likely to kill the project: a generator that overwrites hand edits is a generator nobody runs twice. Decide where the line between generated and hand-written code sits before you write the emitter, and make regenerating into a scratch tree and diffing the ordinary path rather than the recovery procedure. ### Stack (fixed, do not substitute) - Builder runtime: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose · the builder is a small internal web app and deserves boring infrastructure - App target: React Native through Expo, emitted as a project you run with the standard tooling · generating into a framework's own conventions means its documentation is also yours - Vocabulary: A fixed, versioned set of components with typed props, not a freeform canvas · a bounded vocabulary is the only way generated code stays readable - Emitter: Deterministic output into a scratch tree, then a three-way merge against your working tree · regeneration has to be safe or it will only ever be run once - Data: A server-side connector proxy holding the credentials, with revocable per-user app tokens · everything bundled into the app is public and the connector key must not be ### Data model (create this before Phase 1) - projects(id, name, expo_sdk, rn_version, emitter_version, created_at) · the framework versions are recorded because generated code is only valid against the versions it targeted - screens(id, project_id, name, route, tree_json, updated_at) · a screen is a tree of vocabulary components and nothing else - components(name, version, category, props_schema, emitter_ref) · the vocabulary, versioned so a project pinned to an older one still generates - sources(id, project_id, kind, base_url, credential_ref, field_map_json) · credential_ref points into the server's secret store and is never serialised into a screen tree - bindings(id, screen_id, node_path, source_id, field, transform) · which component property reads which field - app_tokens(id, project_id, subject, issued_at, last_used_at, revoked_at) · what the installed app actually holds - generations(id, project_id, at, emitter_version, files_written, files_skipped, conflicts_json) · every regeneration, including what it refused to touch ### Phase 1 · A vocabulary, not a canvas Build: Define perhaps a dozen components with typed property schemas: screen, stack, row, text, image, field, button, list, list item, form. Validate every screen tree against those schemas on save. There is no absolute positioning and no arbitrary style object. Trap: Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. Done when: A screen tree that references an unknown component or an invalid property is refused on save with a message naming the node, tested. No component accepts an arbitrary style object, asserted by scanning the property schemas in the suite. The vocabulary is versioned and a project records which version it targets. A tree round-trips through save and load unchanged, tested. Do not build yet: No bindings, no generation, no connector and no tokens yet. ### Phase 2 · Screens that bind to fields Build: Define a source by its base address and a field map. Bind a component property to a field by path. Preview a screen against a sample response captured from the source, so the layout is judged against real data. A binding to a field that is absent from the map is a validation error. Trap: Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. Done when: A binding to a field not present in the source's field map fails validation with the node path named, tested. The preview renders against a captured sample response rather than static placeholders, verified by changing the sample and seeing the preview change. Every source has at least one sample response stored, enforced before a screen can bind to it. Empty and null field values render a defined state rather than a blank, tested with an awkward record. Do not build yet: No code generation, no merging and no connector proxy yet. ### Phase 3 · An emitter you can diff Build: Emit one file per screen plus a shared components directory. Sort every map before serialising, generate stable component and variable names from node identifiers, and run the output through a formatter as the last step. Record the emitter version in a header comment in every file. Trap: Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. Done when: Generating the same project twice produces byte-identical files, asserted in the suite. Generated source passes the project's own type check and formatter with no changes. Every generated file carries the emitter version and the vocabulary version it was produced against. Renaming a screen changes only that screen's file and the route table, verified by diffing a regeneration. Do not build yet: No merge handling, no connector and no export yet. ### Phase 4 · Regeneration that keeps your edits Build: Generate into a scratch tree. Compare three ways: the last generated output, the current working tree and the new output. Apply files that you have not touched, leave conflicts in place with a clear report, and never write a file whose current contents differ from what was last generated without saying so. Keep the last generated tree so the comparison is possible at all. Trap: Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. Done when: Regenerating over a file edited by hand leaves that file untouched and reports it as a conflict, tested by editing a generated file and regenerating. Files unchanged since the last generation are updated in place. The generations table records files written, files skipped and conflicts for every run. A project with no previous generated tree refuses to regenerate over existing files until one is recorded, tested. Do not build yet: No connector proxy, no tokens and no export yet. ### Phase 5 · The credential stays home Build: Every data request from the app goes to your connector proxy, which holds the source credential server-side, applies the field map and returns only mapped fields. The app carries a per-user token that can be revoked and is checked on every request. Export the whole project: screen trees, generated source, the connector configuration without secrets, and instructions to run it alone. Trap: Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. Done when: No generated file and no bundled environment variable contains a source credential, asserted by a test that scans the emitted tree and the build environment. Revoking an app token causes the next request from that app to fail with a clear status, tested end to end. The proxy returns only fields present in the source's field map, tested by adding an extra field to a stubbed response and checking it is dropped. The exported project builds and runs against the proxy with the builder stopped, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Running arbitrary user-authored code inside the builder, which turns an internal tool into a sandbox problem and a very different security posture. - A broad connector marketplace, because dozens of integrations that keep working is the maintenance the subscription actually pays for. - App store builds and signing, which are credential management, review queues and a release cadence rather than a generator feature. - Pixel-level visual design, since a bounded vocabulary is what keeps the generated code readable and an escape hatch removes the point of generating at all. ### README must contain - That anything bundled into the app is readable by anyone who installs it, quoted from the framework's own documentation, and that this is why the connector runs server-side - That the app holds a revocable per-user token and how to revoke one - That regeneration compares three ways against the last generated tree, and what a reported conflict means - Which framework and vocabulary versions a project targets, and that generated code is only valid against those - How to take the exported project and run it with the builder switched off permanently ===== ARCHITECTURE.md ===== # Architecture · Draftbit ## Stack | Part | Choice | Why | | --- | --- | --- | | Builder runtime | Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose | the builder is a small internal web app and deserves boring infrastructure | | App target | React Native through Expo, emitted as a project you run with the standard tooling | generating into a framework's own conventions means its documentation is also yours | | Vocabulary | A fixed, versioned set of components with typed props, not a freeform canvas | a bounded vocabulary is the only way generated code stays readable | | Emitter | Deterministic output into a scratch tree, then a three-way merge against your working tree | regeneration has to be safe or it will only ever be run once | | Data | A server-side connector proxy holding the credentials, with revocable per-user app tokens | everything bundled into the app is public and the connector key must not be | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Vocabulary | Typed components and their schemas. | Any set, provided it stays closed and versioned. | | Bindings | Field paths, maps and sample responses. | Any binding scheme, provided a bad field path fails validation. | | Emitter | Deterministic source generation. | Any target framework, provided two runs are byte-identical. | | Merge | The baseline tree, three-way comparison and conflicts. | Not swappable for overwriting, which is the failure this exists to prevent. | | Connector | Server-side credentials, field mapping and app tokens. | Any proxy, provided no credential is ever bundled. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · The builder's own database. Screens, bindings and generation history. - `SECRET_STORE_KEY` · required, secret · Encrypts connector credentials at rest. Losing this means re-entering every source credential. - `CONNECTOR_BASE_URL` · required · Where the generated app sends data requests. The only address it knows. - `APP_TOKEN_TTL_DAYS` · required · How long an app token lives before it must be reissued. - `GENERATED_ROOT` · required · Where projects are emitted. The generator owns this tree and nothing above it. - `BASELINE_ROOT` · required · The last generated tree, kept as the merge base. Deleting this disables safe regeneration. ## 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 · Draftbit product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose, React Native through Expo, emitted as a project you run with the standard tooling, A fixed, versioned set of components with typed props, not a freeform canvas, Deterministic output into a scratch tree, then a three-way merge against your working tree, A server-side connector proxy holding the credentials, with revocable per-user app tokens. - 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 - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. - Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. ===== MILESTONES.md ===== # Delivery milestones · Draftbit Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A vocabulary, not a canvas A screen is a tree of a small number of typed components, and anything the vocabulary cannot express is refused rather than approximated. ### Steps 1. Write the vocabulary as schemas first The schema is the contract between the builder and the emitter. Files: `src/vocabulary/components.ts`, `tests/vocabulary.test.ts` 2. Validate every tree on save An invalid tree reaches the emitter and becomes invalid code. Files: `src/screens/validate.ts` 3. Version the vocabulary and pin projects to it Otherwise adding a component changes every existing project. Files: `src/db/migrations/001_vocabulary.sql` ### Done when - [ ] Unknown components and properties are refused on save - [ ] No component accepts a freeform style object - [ ] The vocabulary is versioned and pinned per project - [ ] Screen trees round-trip unchanged ### Watch out - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. ## M2 · Screens that bind to fields A component property can read a field from a data source, and the builder shows real shapes rather than placeholder text. ### Steps 1. Bind by field path against a declared map A typo in a field name should be a validation error, not an empty screen. Files: `src/bindings/validate.ts`, `tests/bindings.test.ts` 2. Preview against a captured real response Placeholder data flatters every layout. Files: `src/preview/sample.ts` 3. Require an awkward record in every sample Long, empty and null are the three that break layouts. Files: `src/sources/samples.ts` ### Done when - [ ] Unknown field bindings fail validation - [ ] Preview uses a captured sample response - [ ] Every source has a stored sample - [ ] Empty and null values render a defined state ### Watch out - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. ## M3 · An emitter you can diff The same screen tree always produces byte-identical source, formatted and readable. ### Steps 1. Sort everything before you serialise it A diff nobody reads is a generator nobody trusts. Files: `src/emit/serialise.ts`, `tests/deterministic-emit.test.ts` 2. Derive names from stable identifiers Generated names that shuffle are the same bug wearing a hat. Files: `src/emit/names.ts` 3. Stamp the emitter and vocabulary versions into every file So you can tell what produced a file you are staring at in six months. Files: `src/emit/header.ts` ### Done when - [ ] Two generations produce byte-identical output - [ ] Generated code passes type check and formatter - [ ] Every file records emitter and vocabulary versions - [ ] Renaming a screen touches one file and the routes ### Watch out - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. ## M4 · Regeneration that keeps your edits Running the generator over a project you have edited by hand never silently loses a line. ### Steps 1. Keep the last generated tree as the merge base Without it you cannot tell your edit from the generator's. Files: `src/generate/baseline.ts` 2. Compare three ways and refuse to guess Silent overwriting is the failure people never forgive. Files: `src/generate/merge.ts`, `tests/merge.test.ts` 3. Report every skip and conflict, and store it The report is what makes regeneration something you dare to run. Files: `src/generate/report.ts` ### Done when - [ ] A hand-edited file survives regeneration - [ ] Untouched files are updated in place - [ ] Writes, skips and conflicts are recorded per run - [ ] Regeneration without a baseline is refused ### Watch out - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ## M5 · The credential stays home (production only) The installed app holds a revocable token and nothing else, and the whole project can be taken away from the builder. ### Steps 1. Put the connector behind a server-side proxy The bundle is public; your server is not. Files: `src/connector/proxy.ts`, `tests/no-secrets-in-bundle.test.ts` 2. Issue per-user tokens you can revoke instantly Rotating a bundled key means shipping an app update. Files: `src/tokens/issue.ts` 3. Make the exported project run without the builder A builder you cannot leave is a builder you should not adopt. Files: `src/export/project.ts`, `docs/standalone.md` ### Done when - [ ] No credential appears in generated code or the bundle - [ ] A revoked token fails the next request - [ ] The proxy returns only mapped fields - [ ] The exported project runs with the builder stopped ### Watch out - Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. ===== OPERATIONS.md ===== # Operations · Draftbit ## Backup The builder database and the baseline tree together, because they only make sense as a pair: the database describes the screens and the baseline is what regeneration compares against. Back up the secret store key separately and somewhere you will still have it. ## Restore Restore the database and the baseline, then generate once and confirm the output is byte-identical to the baseline. If it is not, the emitter version moved while the backup sat still, and you need to regenerate deliberately and read the diff rather than letting a merge sort it out. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Conflicts per generation, app tokens used after their project stopped being deployed, and any request the proxy refused because the field was not in the map. The second one is how you find installations you forgot about. ## Incident checklist The incident that matters is a credential in a bundle. If it happens, revoking the app tokens does nothing because the credential is the source's, not yours to revoke selectively: you rotate at the source, ship a new build, and the gap is why the proxy exists in the first place. 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 - [ ] No credential is confirmed present in generated code or the bundle - [ ] Two generations are confirmed byte-identical - [ ] A hand-edited file is confirmed to survive regeneration - [ ] A revoked app token is confirmed to fail the next request - [ ] The exported project is confirmed to run with the builder stopped ## Launch constraint Do not market omitted Draftbit 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 · secret. The builder's own database. Screens, bindings and generation history. DATABASE_URL=postgres://builder:...@db:5432/builder # Required · secret. Encrypts connector credentials at rest. Losing this means re-entering every source credential. SECRET_STORE_KEY=base64:...redacted # Required. Where the generated app sends data requests. The only address it knows. CONNECTOR_BASE_URL=https://tools.internal.example/connector # Required. How long an app token lives before it must be reissued. APP_TOKEN_TTL_DAYS=30 # Required. Where projects are emitted. The generator owns this tree and nothing above it. GENERATED_ROOT=./generated # Required. The last generated tree, kept as the merge base. Deleting this disables safe regeneration. BASELINE_ROOT=./generated-baseline
# Draftbit · indie build A constrained app builder whose hard problems are an emitter whose output is stable enough to diff, a regeneration path that cannot destroy hand-written code, and a data connector whose credentials never leave your server. 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 | | --- | --- | --- | | Builder runtime | Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose | the builder is a small internal web app and deserves boring infrastructure | | App target | React Native through Expo, emitted as a project you run with the standard tooling | generating into a framework's own conventions means its documentation is also yours | | Vocabulary | A fixed, versioned set of components with typed props, not a freeform canvas | a bounded vocabulary is the only way generated code stays readable | | Emitter | Deterministic output into a scratch tree, then a three-way merge against your working tree | regeneration has to be safe or it will only ever be run once | | Data | A server-side connector proxy holding the credentials, with revocable per-user app tokens | everything bundled into the app is public and the connector key must not be | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Docker and Docker Compose** · free - Why: Runs the builder and its database with one command, which is the difference between a tool you use and a tool you set up once. - Get it: Install Docker Desktop or Docker Engine with the compose plugin. - Verify: docker compose version prints - [ ] **Node.js 22 or newer and the Expo command line tools** · free - Why: Builds the builder and runs the generated project. - Get it: nodejs.org, then the Expo tooling in the generated project. - Verify: A freshly generated project starts and loads on a device - [ ] **A phone with the development client installed** · free - Why: Layouts that look fine in a browser preview are routinely wrong on a real screen, and the awkward-record problem only shows up there. - Get it: Install the development client on your own phone and connect it to the local server. - Verify: A generated screen renders on the phone with the sample data - [ ] **Credentials for one REST data source** · varies - Why: One connector done properly is the whole lesson; the credential is also the thing this build exists to keep off the device. - Get it: An API key for a service you control, with the narrowest permissions that serve the screens you want. - Verify: The proxy returns mapped fields and the key appears nowhere in the generated tree - [ ] **Where generated code ends and hand-written code begins** · free - Why: Every regeneration decision follows from this line, and deciding it after the emitter exists means deciding it badly. - Get it: Write down which directories the generator owns outright and which it must never write to. - Verify: The rule is in the README and enforced by the merge step ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. - visual mobile builder, device previews, integrations, collaboration, and build tooling - hundreds of connectors - pixel-perfect app design - mobile app-store builds - enterprise permissions and scale If one of those is essential to you, that is the reason to keep paying for Draftbit, and the README should say so rather than pretend.
# Build brief · Draftbit 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 screen composer that emits readable React Native source and never overwrites what you edited by hand to replace Draftbit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero What this builds gets installed on devices you do not control, and that one fact decides where credentials live. Expo's own documentation is blunt about it: do not store sensitive information such as private keys in the publicly prefixed environment variables, because those variables will be visible in plain text in your compiled application, and when an end user runs your app they have access to all of the code and embedded environment variables in it. That is not specific to one framework. Anything you bundle ships to everyone who installs the app. So the connector's credential never reaches the generated app. The connector runs on your server, the app holds a per-user token you can revoke, and every outbound request is authorised on your side. A builder that lets somebody paste an API key into a data source field and then bundles it has just published that key. Second rule, less dramatic and more likely to kill the project: a generator that overwrites hand edits is a generator nobody runs twice. Decide where the line between generated and hand-written code sits before you write the emitter, and make regenerating into a scratch tree and diffing the ordinary path rather than the recovery procedure. ### Stack (fixed, do not substitute) - Builder runtime: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose · the builder is a small internal web app and deserves boring infrastructure - App target: React Native through Expo, emitted as a project you run with the standard tooling · generating into a framework's own conventions means its documentation is also yours - Vocabulary: A fixed, versioned set of components with typed props, not a freeform canvas · a bounded vocabulary is the only way generated code stays readable - Emitter: Deterministic output into a scratch tree, then a three-way merge against your working tree · regeneration has to be safe or it will only ever be run once - Data: A server-side connector proxy holding the credentials, with revocable per-user app tokens · everything bundled into the app is public and the connector key must not be ### Data model (create this before Phase 1) - projects(id, name, expo_sdk, rn_version, emitter_version, created_at) · the framework versions are recorded because generated code is only valid against the versions it targeted - screens(id, project_id, name, route, tree_json, updated_at) · a screen is a tree of vocabulary components and nothing else - components(name, version, category, props_schema, emitter_ref) · the vocabulary, versioned so a project pinned to an older one still generates - sources(id, project_id, kind, base_url, credential_ref, field_map_json) · credential_ref points into the server's secret store and is never serialised into a screen tree - bindings(id, screen_id, node_path, source_id, field, transform) · which component property reads which field - app_tokens(id, project_id, subject, issued_at, last_used_at, revoked_at) · what the installed app actually holds - generations(id, project_id, at, emitter_version, files_written, files_skipped, conflicts_json) · every regeneration, including what it refused to touch ### Phase 1 · A vocabulary, not a canvas Build: Define perhaps a dozen components with typed property schemas: screen, stack, row, text, image, field, button, list, list item, form. Validate every screen tree against those schemas on save. There is no absolute positioning and no arbitrary style object. Trap: Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. Done when: A screen tree that references an unknown component or an invalid property is refused on save with a message naming the node, tested. No component accepts an arbitrary style object, asserted by scanning the property schemas in the suite. The vocabulary is versioned and a project records which version it targets. A tree round-trips through save and load unchanged, tested. Do not build yet: No bindings, no generation, no connector and no tokens yet. ### Phase 2 · Screens that bind to fields Build: Define a source by its base address and a field map. Bind a component property to a field by path. Preview a screen against a sample response captured from the source, so the layout is judged against real data. A binding to a field that is absent from the map is a validation error. Trap: Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. Done when: A binding to a field not present in the source's field map fails validation with the node path named, tested. The preview renders against a captured sample response rather than static placeholders, verified by changing the sample and seeing the preview change. Every source has at least one sample response stored, enforced before a screen can bind to it. Empty and null field values render a defined state rather than a blank, tested with an awkward record. Do not build yet: No code generation, no merging and no connector proxy yet. ### Phase 3 · An emitter you can diff Build: Emit one file per screen plus a shared components directory. Sort every map before serialising, generate stable component and variable names from node identifiers, and run the output through a formatter as the last step. Record the emitter version in a header comment in every file. Trap: Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. Done when: Generating the same project twice produces byte-identical files, asserted in the suite. Generated source passes the project's own type check and formatter with no changes. Every generated file carries the emitter version and the vocabulary version it was produced against. Renaming a screen changes only that screen's file and the route table, verified by diffing a regeneration. Do not build yet: No merge handling, no connector and no export yet. ### Phase 4 · Regeneration that keeps your edits Build: Generate into a scratch tree. Compare three ways: the last generated output, the current working tree and the new output. Apply files that you have not touched, leave conflicts in place with a clear report, and never write a file whose current contents differ from what was last generated without saying so. Keep the last generated tree so the comparison is possible at all. Trap: Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. Done when: Regenerating over a file edited by hand leaves that file untouched and reports it as a conflict, tested by editing a generated file and regenerating. Files unchanged since the last generation are updated in place. The generations table records files written, files skipped and conflicts for every run. A project with no previous generated tree refuses to regenerate over existing files until one is recorded, tested. Do not build yet: No connector proxy, no tokens and no export yet. ### Phase 5 · The credential stays home Build: Every data request from the app goes to your connector proxy, which holds the source credential server-side, applies the field map and returns only mapped fields. The app carries a per-user token that can be revoked and is checked on every request. Export the whole project: screen trees, generated source, the connector configuration without secrets, and instructions to run it alone. Trap: Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. Done when: No generated file and no bundled environment variable contains a source credential, asserted by a test that scans the emitted tree and the build environment. Revoking an app token causes the next request from that app to fail with a clear status, tested end to end. The proxy returns only fields present in the source's field map, tested by adding an extra field to a stubbed response and checking it is dropped. The exported project builds and runs against the proxy with the builder stopped, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Running arbitrary user-authored code inside the builder, which turns an internal tool into a sandbox problem and a very different security posture. - A broad connector marketplace, because dozens of integrations that keep working is the maintenance the subscription actually pays for. - App store builds and signing, which are credential management, review queues and a release cadence rather than a generator feature. - Pixel-level visual design, since a bounded vocabulary is what keeps the generated code readable and an escape hatch removes the point of generating at all. ### README must contain - That anything bundled into the app is readable by anyone who installs it, quoted from the framework's own documentation, and that this is why the connector runs server-side - That the app holds a revocable per-user token and how to revoke one - That regeneration compares three ways against the last generated tree, and what a reported conflict means - Which framework and vocabulary versions a project targets, and that generated code is only valid against those - How to take the exported project and run it with the builder switched off permanently
# Agent instructions · Draftbit indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose, React Native through Expo, emitted as a project you run with the standard tooling, A fixed, versioned set of components with typed props, not a freeform canvas, Deterministic output into a scratch tree, then a three-way merge against your working tree, A server-side connector proxy holding the credentials, with revocable per-user app tokens. 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 - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write.
# Build plan · Draftbit A constrained app builder whose hard problems are an emitter whose output is stable enough to diff, a regeneration path that cannot destroy hand-written code, and a data connector whose credentials never leave your server. 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 vocabulary, not a canvas A screen is a tree of a small number of typed components, and anything the vocabulary cannot express is refused rather than approximated. ### Steps 1. Write the vocabulary as schemas first The schema is the contract between the builder and the emitter. Files: `src/vocabulary/components.ts`, `tests/vocabulary.test.ts` 2. Validate every tree on save An invalid tree reaches the emitter and becomes invalid code. Files: `src/screens/validate.ts` 3. Version the vocabulary and pin projects to it Otherwise adding a component changes every existing project. Files: `src/db/migrations/001_vocabulary.sql` ### Done when - [ ] Unknown components and properties are refused on save - [ ] No component accepts a freeform style object - [ ] The vocabulary is versioned and pinned per project - [ ] Screen trees round-trip unchanged ### Watch out - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. ## Phase 2 · Screens that bind to fields A component property can read a field from a data source, and the builder shows real shapes rather than placeholder text. ### Steps 1. Bind by field path against a declared map A typo in a field name should be a validation error, not an empty screen. Files: `src/bindings/validate.ts`, `tests/bindings.test.ts` 2. Preview against a captured real response Placeholder data flatters every layout. Files: `src/preview/sample.ts` 3. Require an awkward record in every sample Long, empty and null are the three that break layouts. Files: `src/sources/samples.ts` ### Done when - [ ] Unknown field bindings fail validation - [ ] Preview uses a captured sample response - [ ] Every source has a stored sample - [ ] Empty and null values render a defined state ### Watch out - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. ## Phase 3 · An emitter you can diff The same screen tree always produces byte-identical source, formatted and readable. ### Steps 1. Sort everything before you serialise it A diff nobody reads is a generator nobody trusts. Files: `src/emit/serialise.ts`, `tests/deterministic-emit.test.ts` 2. Derive names from stable identifiers Generated names that shuffle are the same bug wearing a hat. Files: `src/emit/names.ts` 3. Stamp the emitter and vocabulary versions into every file So you can tell what produced a file you are staring at in six months. Files: `src/emit/header.ts` ### Done when - [ ] Two generations produce byte-identical output - [ ] Generated code passes type check and formatter - [ ] Every file records emitter and vocabulary versions - [ ] Renaming a screen touches one file and the routes ### Watch out - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. ## Phase 4 · Regeneration that keeps your edits Running the generator over a project you have edited by hand never silently loses a line. ### Steps 1. Keep the last generated tree as the merge base Without it you cannot tell your edit from the generator's. Files: `src/generate/baseline.ts` 2. Compare three ways and refuse to guess Silent overwriting is the failure people never forgive. Files: `src/generate/merge.ts`, `tests/merge.test.ts` 3. Report every skip and conflict, and store it The report is what makes regeneration something you dare to run. Files: `src/generate/report.ts` ### Done when - [ ] A hand-edited file survives regeneration - [ ] Untouched files are updated in place - [ ] Writes, skips and conflicts are recorded per run - [ ] Regeneration without a baseline is refused ### Watch out - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ## Not in this build - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. ## After v1, if you want it - A second data source kind behind the same field map, once the proxy has proved the pattern with one - A vocabulary component that wraps a hand-written file, which is the honest way to add an escape hatch without opening the canvas - A generated test per screen that renders it against the stored sample, which costs little and catches the awkward record
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. The builder's own database. Screens, bindings and generation history. DATABASE_URL=postgres://builder:...@db:5432/builder # Required · secret. Encrypts connector credentials at rest. Losing this means re-entering every source credential. SECRET_STORE_KEY=base64:...redacted # Required. Where the generated app sends data requests. The only address it knows. CONNECTOR_BASE_URL=https://tools.internal.example/connector # Required. How long an app token lives before it must be reissued. APP_TOKEN_TTL_DAYS=30 # Required. Where projects are emitted. The generator owns this tree and nothing above it. GENERATED_ROOT=./generated # Required. The last generated tree, kept as the merge base. Deleting this disables safe regeneration. BASELINE_ROOT=./generated-baseline
# Draftbit · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Draftbit, compose a React Native app from constrained screens and export the source code. The hard boundary is visual mobile builder, device previews, integrations, collaboration, and build tooling, plus connectors, runtime reliability, and governance. ## Product outcome A builder whose generated code is readable and diffable, whose regeneration cannot destroy your edits, and whose apps ship with a revocable token instead of a credential anyone can read out of the bundle. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Docker - PostgreSQL - self-hosted deploy target - API credentials for one selected integration ## Explicit non-goals for v1 - Arbitrary user-authored code execution. - A broad connector marketplace. - App store builds and signing. - Pixel-level visual design with a freeform canvas. - visual mobile builder, device previews, integrations, collaboration, and build tooling - hundreds of connectors - pixel-perfect app design - mobile app-store builds - enterprise permissions and scale ## Success criteria - No credential is confirmed present in generated code or the bundle - Two generations are confirmed byte-identical - A hand-edited file is confirmed to survive regeneration - A revoked app token is confirmed to fail the next request - The exported project is confirmed to run with the builder stopped
# Build brief · Draftbit 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 screen composer that emits readable React Native source and never overwrites what you edited by hand to replace Draftbit. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero What this builds gets installed on devices you do not control, and that one fact decides where credentials live. Expo's own documentation is blunt about it: do not store sensitive information such as private keys in the publicly prefixed environment variables, because those variables will be visible in plain text in your compiled application, and when an end user runs your app they have access to all of the code and embedded environment variables in it. That is not specific to one framework. Anything you bundle ships to everyone who installs the app. So the connector's credential never reaches the generated app. The connector runs on your server, the app holds a per-user token you can revoke, and every outbound request is authorised on your side. A builder that lets somebody paste an API key into a data source field and then bundles it has just published that key. Second rule, less dramatic and more likely to kill the project: a generator that overwrites hand edits is a generator nobody runs twice. Decide where the line between generated and hand-written code sits before you write the emitter, and make regenerating into a scratch tree and diffing the ordinary path rather than the recovery procedure. ### Stack (fixed, do not substitute) - Builder runtime: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose · the builder is a small internal web app and deserves boring infrastructure - App target: React Native through Expo, emitted as a project you run with the standard tooling · generating into a framework's own conventions means its documentation is also yours - Vocabulary: A fixed, versioned set of components with typed props, not a freeform canvas · a bounded vocabulary is the only way generated code stays readable - Emitter: Deterministic output into a scratch tree, then a three-way merge against your working tree · regeneration has to be safe or it will only ever be run once - Data: A server-side connector proxy holding the credentials, with revocable per-user app tokens · everything bundled into the app is public and the connector key must not be ### Data model (create this before Phase 1) - projects(id, name, expo_sdk, rn_version, emitter_version, created_at) · the framework versions are recorded because generated code is only valid against the versions it targeted - screens(id, project_id, name, route, tree_json, updated_at) · a screen is a tree of vocabulary components and nothing else - components(name, version, category, props_schema, emitter_ref) · the vocabulary, versioned so a project pinned to an older one still generates - sources(id, project_id, kind, base_url, credential_ref, field_map_json) · credential_ref points into the server's secret store and is never serialised into a screen tree - bindings(id, screen_id, node_path, source_id, field, transform) · which component property reads which field - app_tokens(id, project_id, subject, issued_at, last_used_at, revoked_at) · what the installed app actually holds - generations(id, project_id, at, emitter_version, files_written, files_skipped, conflicts_json) · every regeneration, including what it refused to touch ### Phase 1 · A vocabulary, not a canvas Build: Define perhaps a dozen components with typed property schemas: screen, stack, row, text, image, field, button, list, list item, form. Validate every screen tree against those schemas on save. There is no absolute positioning and no arbitrary style object. Trap: Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. Done when: A screen tree that references an unknown component or an invalid property is refused on save with a message naming the node, tested. No component accepts an arbitrary style object, asserted by scanning the property schemas in the suite. The vocabulary is versioned and a project records which version it targets. A tree round-trips through save and load unchanged, tested. Do not build yet: No bindings, no generation, no connector and no tokens yet. ### Phase 2 · Screens that bind to fields Build: Define a source by its base address and a field map. Bind a component property to a field by path. Preview a screen against a sample response captured from the source, so the layout is judged against real data. A binding to a field that is absent from the map is a validation error. Trap: Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. Done when: A binding to a field not present in the source's field map fails validation with the node path named, tested. The preview renders against a captured sample response rather than static placeholders, verified by changing the sample and seeing the preview change. Every source has at least one sample response stored, enforced before a screen can bind to it. Empty and null field values render a defined state rather than a blank, tested with an awkward record. Do not build yet: No code generation, no merging and no connector proxy yet. ### Phase 3 · An emitter you can diff Build: Emit one file per screen plus a shared components directory. Sort every map before serialising, generate stable component and variable names from node identifiers, and run the output through a formatter as the last step. Record the emitter version in a header comment in every file. Trap: Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. Done when: Generating the same project twice produces byte-identical files, asserted in the suite. Generated source passes the project's own type check and formatter with no changes. Every generated file carries the emitter version and the vocabulary version it was produced against. Renaming a screen changes only that screen's file and the route table, verified by diffing a regeneration. Do not build yet: No merge handling, no connector and no export yet. ### Phase 4 · Regeneration that keeps your edits Build: Generate into a scratch tree. Compare three ways: the last generated output, the current working tree and the new output. Apply files that you have not touched, leave conflicts in place with a clear report, and never write a file whose current contents differ from what was last generated without saying so. Keep the last generated tree so the comparison is possible at all. Trap: Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. Done when: Regenerating over a file edited by hand leaves that file untouched and reports it as a conflict, tested by editing a generated file and regenerating. Files unchanged since the last generation are updated in place. The generations table records files written, files skipped and conflicts for every run. A project with no previous generated tree refuses to regenerate over existing files until one is recorded, tested. Do not build yet: No connector proxy, no tokens and no export yet. ### Phase 5 · The credential stays home Build: Every data request from the app goes to your connector proxy, which holds the source credential server-side, applies the field map and returns only mapped fields. The app carries a per-user token that can be revoked and is checked on every request. Export the whole project: screen trees, generated source, the connector configuration without secrets, and instructions to run it alone. Trap: Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second. Done when: No generated file and no bundled environment variable contains a source credential, asserted by a test that scans the emitted tree and the build environment. Revoking an app token causes the next request from that app to fail with a clear status, tested end to end. The proxy returns only fields present in the source's field map, tested by adding an extra field to a stubbed response and checking it is dropped. The exported project builds and runs against the proxy with the builder stopped, tested. Do not build yet: Nothing after this. ### Out of scope (and why) - Running arbitrary user-authored code inside the builder, which turns an internal tool into a sandbox problem and a very different security posture. - A broad connector marketplace, because dozens of integrations that keep working is the maintenance the subscription actually pays for. - App store builds and signing, which are credential management, review queues and a release cadence rather than a generator feature. - Pixel-level visual design, since a bounded vocabulary is what keeps the generated code readable and an escape hatch removes the point of generating at all. ### README must contain - That anything bundled into the app is readable by anyone who installs it, quoted from the framework's own documentation, and that this is why the connector runs server-side - That the app holds a revocable per-user token and how to revoke one - That regeneration compares three ways against the last generated tree, and what a reported conflict means - Which framework and vocabulary versions a project targets, and that generated code is only valid against those - How to take the exported project and run it with the builder switched off permanently
# Architecture · Draftbit ## Stack | Part | Choice | Why | | --- | --- | --- | | Builder runtime | Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose | the builder is a small internal web app and deserves boring infrastructure | | App target | React Native through Expo, emitted as a project you run with the standard tooling | generating into a framework's own conventions means its documentation is also yours | | Vocabulary | A fixed, versioned set of components with typed props, not a freeform canvas | a bounded vocabulary is the only way generated code stays readable | | Emitter | Deterministic output into a scratch tree, then a three-way merge against your working tree | regeneration has to be safe or it will only ever be run once | | Data | A server-side connector proxy holding the credentials, with revocable per-user app tokens | everything bundled into the app is public and the connector key must not be | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Vocabulary | Typed components and their schemas. | Any set, provided it stays closed and versioned. | | Bindings | Field paths, maps and sample responses. | Any binding scheme, provided a bad field path fails validation. | | Emitter | Deterministic source generation. | Any target framework, provided two runs are byte-identical. | | Merge | The baseline tree, three-way comparison and conflicts. | Not swappable for overwriting, which is the failure this exists to prevent. | | Connector | Server-side credentials, field mapping and app tokens. | Any proxy, provided no credential is ever bundled. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · The builder's own database. Screens, bindings and generation history. - `SECRET_STORE_KEY` · required, secret · Encrypts connector credentials at rest. Losing this means re-entering every source credential. - `CONNECTOR_BASE_URL` · required · Where the generated app sends data requests. The only address it knows. - `APP_TOKEN_TTL_DAYS` · required · How long an app token lives before it must be reissued. - `GENERATED_ROOT` · required · Where projects are emitted. The generator owns this tree and nothing above it. - `BASELINE_ROOT` · required · The last generated tree, kept as the merge base. Deleting this disables safe regeneration. ## 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 · Draftbit product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker Compose, React Native through Expo, emitted as a project you run with the standard tooling, A fixed, versioned set of components with typed props, not a freeform canvas, Deterministic output into a scratch tree, then a three-way merge against your working tree, A server-side connector proxy holding the credentials, with revocable per-user app tokens. - 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 - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. - Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second.
# Delivery milestones · Draftbit Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A vocabulary, not a canvas A screen is a tree of a small number of typed components, and anything the vocabulary cannot express is refused rather than approximated. ### Steps 1. Write the vocabulary as schemas first The schema is the contract between the builder and the emitter. Files: `src/vocabulary/components.ts`, `tests/vocabulary.test.ts` 2. Validate every tree on save An invalid tree reaches the emitter and becomes invalid code. Files: `src/screens/validate.ts` 3. Version the vocabulary and pin projects to it Otherwise adding a component changes every existing project. Files: `src/db/migrations/001_vocabulary.sql` ### Done when - [ ] Unknown components and properties are refused on save - [ ] No component accepts a freeform style object - [ ] The vocabulary is versioned and pinned per project - [ ] Screen trees round-trip unchanged ### Watch out - Adding free positioning and an open style object the first time a layout is awkward turns this into a worse design tool whose generated code is a wall of unreadable inline styles, which defeats the only reason to generate source rather than render a runtime. Keep the vocabulary closed, and when something genuinely cannot be expressed, add a named component to the vocabulary rather than an escape hatch. ## M2 · Screens that bind to fields A component property can read a field from a data source, and the builder shows real shapes rather than placeholder text. ### Steps 1. Bind by field path against a declared map A typo in a field name should be a validation error, not an empty screen. Files: `src/bindings/validate.ts`, `tests/bindings.test.ts` 2. Preview against a captured real response Placeholder data flatters every layout. Files: `src/preview/sample.ts` 3. Require an awkward record in every sample Long, empty and null are the three that break layouts. Files: `src/sources/samples.ts` ### Done when - [ ] Unknown field bindings fail validation - [ ] Preview uses a captured sample response - [ ] Every source has a stored sample - [ ] Empty and null values render a defined state ### Watch out - Previewing every list against three rows of neat placeholder text produces layouts that fall apart the moment a real record has an empty name, a forty-character title or a null image, and you discover all three after the app is on somebody's phone. Capture a real sample response per source, preview against it, and include at least one deliberately awkward record in the sample. ## M3 · An emitter you can diff The same screen tree always produces byte-identical source, formatted and readable. ### Steps 1. Sort everything before you serialise it A diff nobody reads is a generator nobody trusts. Files: `src/emit/serialise.ts`, `tests/deterministic-emit.test.ts` 2. Derive names from stable identifiers Generated names that shuffle are the same bug wearing a hat. Files: `src/emit/names.ts` 3. Stamp the emitter and vocabulary versions into every file So you can tell what produced a file you are staring at in six months. Files: `src/emit/header.ts` ### Done when - [ ] Two generations produce byte-identical output - [ ] Generated code passes type check and formatter - [ ] Every file records emitter and vocabulary versions - [ ] Renaming a screen touches one file and the routes ### Watch out - Serialising properties in whatever order they happen to sit in memory means two runs over an unchanged project produce a thousand-line diff of reordered attributes, and after the second time nobody looks at a generated diff again, which is exactly when the generator starts quietly breaking things. Sort every collection before emitting, derive names from stable identifiers, and assert byte equality across two runs in the test suite. ## M4 · Regeneration that keeps your edits Running the generator over a project you have edited by hand never silently loses a line. ### Steps 1. Keep the last generated tree as the merge base Without it you cannot tell your edit from the generator's. Files: `src/generate/baseline.ts` 2. Compare three ways and refuse to guess Silent overwriting is the failure people never forgive. Files: `src/generate/merge.ts`, `tests/merge.test.ts` 3. Report every skip and conflict, and store it The report is what makes regeneration something you dare to run. Files: `src/generate/report.ts` ### Done when - [ ] A hand-edited file survives regeneration - [ ] Untouched files are updated in place - [ ] Writes, skips and conflicts are recorded per run - [ ] Regeneration without a baseline is refused ### Watch out - Regenerating straight over the project directory destroys every hand edit the first time somebody fixes a layout in the code, and because generated files look regenerable nobody has them in a separate commit, so the work is simply gone. Keep the previous generated tree, compare three ways, and make an unresolvable difference a reported conflict rather than a write. ## M5 · The credential stays home (production only) The installed app holds a revocable token and nothing else, and the whole project can be taken away from the builder. ### Steps 1. Put the connector behind a server-side proxy The bundle is public; your server is not. Files: `src/connector/proxy.ts`, `tests/no-secrets-in-bundle.test.ts` 2. Issue per-user tokens you can revoke instantly Rotating a bundled key means shipping an app update. Files: `src/tokens/issue.ts` 3. Make the exported project run without the builder A builder you cannot leave is a builder you should not adopt. Files: `src/export/project.ts`, `docs/standalone.md` ### Done when - [ ] No credential appears in generated code or the bundle - [ ] A revoked token fails the next request - [ ] The proxy returns only mapped fields - [ ] The exported project runs with the builder stopped ### Watch out - Putting the source credential into the generated app's environment because it is faster than writing a proxy ships that credential to every installation, and rotating it then means shipping an app update and waiting for people to install it, which is days during which the old key must stay valid. Keep the credential on the server from the first screen, and give the app a token you can kill in a second.
# Operations · Draftbit ## Backup The builder database and the baseline tree together, because they only make sense as a pair: the database describes the screens and the baseline is what regeneration compares against. Back up the secret store key separately and somewhere you will still have it. ## Restore Restore the database and the baseline, then generate once and confirm the output is byte-identical to the baseline. If it is not, the emitter version moved while the backup sat still, and you need to regenerate deliberately and read the diff rather than letting a merge sort it out. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Conflicts per generation, app tokens used after their project stopped being deployed, and any request the proxy refused because the field was not in the map. The second one is how you find installations you forgot about. ## Incident checklist The incident that matters is a credential in a bundle. If it happens, revoking the app tokens does nothing because the credential is the source's, not yours to revoke selectively: you rotate at the source, ship a new build, and the gap is why the proxy exists in the first place. 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 - [ ] No credential is confirmed present in generated code or the bundle - [ ] Two generations are confirmed byte-identical - [ ] A hand-edited file is confirmed to survive regeneration - [ ] A revoked app token is confirmed to fail the next request - [ ] The exported project is confirmed to run with the builder stopped ## Launch constraint Do not market omitted Draftbit 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 · secret. The builder's own database. Screens, bindings and generation history. DATABASE_URL=postgres://builder:...@db:5432/builder # Required · secret. Encrypts connector credentials at rest. Losing this means re-entering every source credential. SECRET_STORE_KEY=base64:...redacted # Required. Where the generated app sends data requests. The only address it knows. CONNECTOR_BASE_URL=https://tools.internal.example/connector # Required. How long an app token lives before it must be reissued. APP_TOKEN_TTL_DAYS=30 # Required. Where projects are emitted. The generator owns this tree and nothing above it. GENERATED_ROOT=./generated # Required. The last generated tree, kept as the merge base. Deleting this disables safe regeneration. BASELINE_ROOT=./generated-baseline
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Draftbit because the builder is only useful when its runtime, connectors, permissions, and deployments remain boring for years. The recurring cost buys schema migration, auth, permissions, connector drift, queues, deployments, backups, and auditability, not just the visible interface.
xvisual mobile builder, device previews, integrations, collaboration, and build tooling
xhundreds of connectors
xpixel-perfect app design
xmobile app-store builds
xenterprise permissions and scale
Draftbit pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/workspace | $0/workspace | 1 concurrent sandbox; 5,000 one-time AI credits; 3 projects; 2 apps/project; 1 REST integration/project; 1 agent |
| standard | $12/workspace | $9.60/workspace | 1 concurrent sandbox; paid project/code-export features; 1 included editor |
| pro | $24/workspace | $19.20/workspace | 3 concurrent sandboxes; 5 agents; 1 included editor |
| team | $120/workspace | $96/workspace | 5 concurrent sandboxes; 10 agents; 10 editors included |
| enterprise | custom | — | Custom sandboxes, credits, seats, support and contract terms |
free tier1 concurrent sandbox; 5,000 one-time credits; 3 projects; 2 apps/project; 1 REST integration/project; 1 agent
billingmonthly + annual (-20%)
hidden costsAI usage consumes credits; top-ups cost roughly $5–$10,000 at 1,000 credits per $1, credits expire after 1 year, and additional editor seats cost $8–$16/month depending on plan
verified 2026-08-14 · source ↗
Vibecode Draftbit
Kinda. The core of Draftbit is buildable in a weekend with the prompt on this page, but there are real gaps: visual mobile builder, device previews, integrations, collaboration, and build tooling, hundreds of connectors. Read the honest list above before committing.
How much does Draftbit cost?
Draftbit costs about $24/month (Pro, checked 2026-08-14), which is $288 per year.
What do I lose by replacing Draftbit?
Honestly: visual mobile builder, device previews, integrations, collaboration, and build tooling; hundreds of connectors; pixel-perfect app design; mobile app-store builds; enterprise permissions and scale. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Draftbit?
Yes: Appsmith (Large open-source low-code platform for internal applications and data sources.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.