SenLang bridge
@atomprd/codegen-senlang is the reference codegen target. It demonstrates how the v0.3 atom set lowers into a SenLang Document v0.3 plus scenario fixtures plus a source-map sidecar.
What it produces
Section titled “What it produces”out/├── app.sen.json # SenLang Document v0.3 (RFC-0008)├── app.sen.map.json # RFC-0009 source map atom-id → sen path└── scenarios/ ├── cri_initial_load_h31a.json # RFC-0010 scenario per criterion └── cri_streak_increments_b2c2.jsonLowering table
Section titled “Lowering table”The deterministic lowering for each Layer 2 field:
| Atom kind | Layer 2 field | Lowers to (SenLang) |
|---|---|---|
feature | behavior.steps | events[<feature_slug>] = [<Action>...] — one Sen Action per step |
feature | behavior.onOk / onErr | Appended to the action chain after the main step block |
ui_view | behavior.screens | View tree under views[<view_slug>]. Multi-screen → top-level $Switch keyed by a state slot |
ui_view | behavior.sections (per screen) | List of Sen-Std component nodes under the screen’s view tree |
rule | behavior.enforced_at[k = ui_disabled] | disabled binding on the matching Button component |
rule | behavior.enforced_at[k = pre_call] | validate Action prepended to the feature’s chain |
rule | behavior.enforced_at[k = validator] | validators[<rule_slug>] registered for the field |
criterion | behavior.before_after | RFC-0010 scenario given.state (before) + when[] (api_calls) + then{} (after) |
fixture | behavior.{shape, before, after, api_calls} | Same scenario shape; sibling file or inline |
Source for the helpers: atomprd/packages/codegen-senlang/src/project/lower.ts.
Step → Action
Section titled “Step → Action”function lowerStep(step: Step): SenAction { switch (step.kind) { case "validate": return { $do: "validate", rule: step.rule, errorMessage: step.errorMessage }; case "call": return { $do: "callApi", method: step.method ?? "GET", endpoint: step.endpoint, body: step.body, assignTo: step.assignTo }; case "dispatch": return { $do: "dispatch", event: step.feature, args: step.args }; case "setState": return { $do: "setState", path: step.path, value: step.value }; case "navigate": return { $do: "navigate", to: step.to, params: step.params }; case "log": return { $do: "log", level: step.level, msg: step.msg }; case "if": return { $do: "if", condition: step.condition, then: step.then.map(lowerStep), else: step.else?.map(lowerStep) }; }}Section → Sen-Std component
Section titled “Section → Sen-Std component”function lowerSection(section: Section): SenNode { switch (section.kind) { case "heading": return { component: "Heading", props: { level: section.level, text: section.text } }; case "text": return { component: "Text", props: { text: section.body } }; case "list": return { component: "List", props: { source: { $ref: section.source } }, children: section.itemTemplate.map(lowerSection) }; case "form": return { component: "Form", props: { fields: section.fields, submitLabel: section.submitLabel, onSubmit: section.submitDispatch ? [{ $do: "dispatch", event: section.submitDispatch }] : undefined } }; case "detail": return { component: "Stack", props: { direction: "column", gap: 2 }, children: section.rows.map((r) => ({ component: "Text", props: { text: `${r.label}: ${r.value}` } })) }; case "button": return { component: "Button", props: { label: section.label, variant: section.variant ?? "primary", onClick: [{ $do: "dispatch", event: section.dispatch }] } }; case "input": return { component: "TextInput", props: { name: section.name, bind: section.bind, placeholder: section.placeholder } }; case "switch": return { component: "Switch", props: { bind: section.bind, label: section.label } }; }}Source map (RFC-0009)
Section titled “Source map (RFC-0009)”app.sen.map.json maps every Sen path back to the atom that produced it:
{ "version": "0.9", "entries": [ { "senPath": "events.feat_create_habit_h14a.0", "atomId": "feat_create_habit_h14a", "field": "behavior.steps.0" }, { "senPath": "events.feat_create_habit_h14a.1", "atomId": "feat_create_habit_h14a", "field": "behavior.steps.1" }, { "senPath": "views.ui_habits_screen_a1b2.list.sections.0", "atomId": "ui_habits_screen_a1b2", "field": "behavior.screens.list.sections.0" } ]}Tools that surface “this Sen path came from atom X” (e.g. the cloud authoring UI’s preview) consume this sidecar.
Pattern B convention defaults (when Layer 2 absent)
Section titled “Pattern B convention defaults (when Layer 2 absent)”| Kind | Behavior absent → bridge emits |
|---|---|
feature | events[<slug>] = [{ $do: "log", level: "warn", msg: "TODO from @atomprd/codegen-senlang ..." }] |
ui_view (viewType=list + rendered_by) | Box + Heading + List + $Each over state.<entitySlot> + empty fallback Text |
ui_view (viewType=form + entity) | TextInput per field + Submit Button |
ui_view (viewType=detail + entity) | Heading + Text rows per field |
ui_view (viewType=dashboard|wizard|custom) | Heading + TODO Text stub |
rule | Preserved in rules[<slug>] but not bound |
criterion | Scenario emits when[] = [{ dispatch: "__todo_<id>" }], then{} = {} |
fixture | Legacy fixture.payload used as scenario.given.state directly |
See also
Section titled “See also”- Examples → Habit Tracker — full round-trip fixture.
- Custom targets — building a target for Vue / NestJS / Flutter.
- SenLang docs — the target format reference.