Build Draftbit

KINDAreplaces $24/mosaves $288/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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-daythe files for this build are in the project pack

Builder runtimeNext.js 15 with TypeScript, PostgreSQL through Drizzle, in Docker ComposeApp targetReact Native through Expo, emitted as a project you run with the standard toolingVocabularyA fixed, versioned set of components with typed props, not a freeform canvasEmitterDeterministic output into a scratch tree, then a three-way merge against your working treeDataA server-side connector proxy holding the credentials, with revocable per-user app tokens

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    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. open ↗

    Verify docker compose version prints

  2. installfree

    Why Builds the builder and runs the generated project.

    Get it nodejs.org, then the Expo tooling in the generated project. open ↗

    Verify A freshly generated project starts and loads on a device

  3. have readyfree

    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

  4. API keyvaries

    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

  5. decidefree

    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

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- 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

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://builder:...@db:5432/builderThe builder's own database. Screens, bindings and generation history.
SECRET_STORE_KEYsecretrequiredbase64:...redactedEncrypts connector credentials at rest. Losing this means re-entering every source credential.
CONNECTOR_BASE_URLrequiredhttps://tools.internal.example/connectorWhere the generated app sends data requests. The only address it knows.
APP_TOKEN_TTL_DAYSrequired30How long an app token lives before it must be reissued.
GENERATED_ROOTrequired./generatedWhere projects are emitted. The generator owns this tree and nothing above it.
BASELINE_ROOTrequired./generated-baselineThe last generated tree, kept as the merge base. Deleting this disables safe regeneration.

The build, in order

  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.

    1. The schema is the contract between the builder and the emitter.

      Files src/vocabulary/components.tstests/vocabulary.test.ts

    2. An invalid tree reaches the emitter and becomes invalid code.

      Files src/screens/validate.ts

    3. Otherwise adding a component changes every existing project.

      Files src/db/migrations/001_vocabulary.sql

    done when · tick each as it passes
    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.
  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.

    1. A typo in a field name should be a validation error, not an empty screen.

      Files src/bindings/validate.tstests/bindings.test.ts

    2. Placeholder data flatters every layout.

      Files src/preview/sample.ts

    3. Long, empty and null are the three that break layouts.

      Files src/sources/samples.ts

    done when · tick each as it passes
    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.
  3. An emitter you can diff

    The same screen tree always produces byte-identical source, formatted and readable.

    1. A diff nobody reads is a generator nobody trusts.

      Files src/emit/serialise.tstests/deterministic-emit.test.ts

    2. Generated names that shuffle are the same bug wearing a hat.

      Files src/emit/names.ts

    3. So you can tell what produced a file you are staring at in six months.

      Files src/emit/header.ts

    done when · tick each as it passes
    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.
  4. Regeneration that keeps your edits

    Running the generator over a project you have edited by hand never silently loses a line.

    1. Without it you cannot tell your edit from the generator's.

      Files src/generate/baseline.ts

    2. Silent overwriting is the failure people never forgive.

      Files src/generate/merge.tstests/merge.test.ts

    3. The report is what makes regeneration something you dare to run.

      Files src/generate/report.ts

    done when · tick each as it passes
    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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more no-code apps.