Skip to content

Quickstart — first project

A 5-minute taste of the loop: author atoms → validate → codegen → run. Uses the SenLang bridge as the target.

For the full walkthrough with a real entity, multiple features and the 6-tab editor, see Examples → Habit Tracker.

  • 1 vision
  • 1 module
  • 1 feature with Layer 2 behavior (a single setState step)
  • 1 criterion linked via verifies

Total: 4 atoms, 1 relation.

  1. Project skeleton.

    Terminal window
    mkdir hello-atoms && cd hello-atoms
    bun init -y
    bun add @atomprd/spec @atomprd/core @atomprd/codegen @atomprd/codegen-senlang
  2. Manifest. Create atoms/manifest.json:

    {
    "version": "0.3",
    "atoms": [
    {
    "id": "vis_main_a1b2",
    "kind": "vision",
    "name": "Hello Atoms",
    "problemStatement": "Show the smallest possible AtomPRD → SenLang round-trip.",
    "valueProposition": "Toy demo."
    },
    {
    "id": "mod_main_c3d4",
    "kind": "module",
    "name": "Counter"
    },
    {
    "id": "feat_increment_e5f6",
    "kind": "feature",
    "name": "Increment counter",
    "intent": {
    "userStory": "As a user I tap a button and the counter goes up by 1."
    },
    "behavior": {
    "steps": [
    { "kind": "setState", "path": "state.count",
    "value": { "$expr": { "op": "+", "args": [{ "$ref": "state.count" }, 1] } } }
    ]
    }
    },
    {
    "id": "cri_increments_g7h8",
    "kind": "criterion",
    "name": "Tapping increment increases count by 1",
    "pattern": "WHEN",
    "actor": "user",
    "trigger": "taps the increment button",
    "system_response": "the count display increases by 1",
    "behavior": {
    "before_after": {
    "state_diff": { "count": { "before": 0, "after": 1 } },
    "api_calls": []
    }
    }
    }
    ],
    "relations": [
    { "type": "verifies", "from": "cri_increments_g7h8", "to": "feat_increment_e5f6" }
    ]
    }
  3. Validate. Create validate.ts:

    import { PrdManifestSchema } from "@atomprd/spec";
    import manifest from "./atoms/manifest.json" assert { type: "json" };
    const r = PrdManifestSchema.safeParse(manifest);
    if (!r.success) { console.error(r.error.issues); process.exit(1); }
    console.log("OK:", r.data.atoms.length, "atoms,", r.data.relations.length, "relations");
    Terminal window
    bun run validate.ts
    # OK: 4 atoms, 1 relations
  4. Codegen. Create gen.ts:

    import { PrdDocument } from "@atomprd/core";
    import { runTemplate } from "@atomprd/codegen";
    import { senlangTemplate } from "@atomprd/codegen-senlang";
    import manifest from "./atoms/manifest.json" assert { type: "json" };
    const doc = PrdDocument.load(manifest);
    const result = await runTemplate(senlangTemplate, {
    doc,
    outDir: "./out",
    });
    console.log(`Wrote ${result.files.length} files to ./out`);
    Terminal window
    bun run gen.ts
    # Wrote N files to ./out
  5. Inspect. You should see out/app.sen.json, out/scenarios/cri_increments_g7h8.json, and a source-map sidecar. Render it:

    src/main.tsx
    import { createRoot } from 'react-dom/client'
    import { SenAppV2 } from '@senlang/renderer-react'
    import doc from '../out/app.sen.json'
    createRoot(document.getElementById('root')!).render(
    <SenAppV2 document={doc} />
    )

That’s the loop end-to-end. The same shape scales up: more atoms, more relations, more behavior steps, more criteria. The bridge stays the same.