Skip to content

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.

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.json

The deterministic lowering for each Layer 2 field:

Atom kindLayer 2 fieldLowers to (SenLang)
featurebehavior.stepsevents[<feature_slug>] = [<Action>...] — one Sen Action per step
featurebehavior.onOk / onErrAppended to the action chain after the main step block
ui_viewbehavior.screensView tree under views[<view_slug>]. Multi-screen → top-level $Switch keyed by a state slot
ui_viewbehavior.sections (per screen)List of Sen-Std component nodes under the screen’s view tree
rulebehavior.enforced_at[k = ui_disabled]disabled binding on the matching Button component
rulebehavior.enforced_at[k = pre_call]validate Action prepended to the feature’s chain
rulebehavior.enforced_at[k = validator]validators[<rule_slug>] registered for the field
criterionbehavior.before_afterRFC-0010 scenario given.state (before) + when[] (api_calls) + then{} (after)
fixturebehavior.{shape, before, after, api_calls}Same scenario shape; sibling file or inline

Source for the helpers: atomprd/packages/codegen-senlang/src/project/lower.ts.

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) };
}
}
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 } };
}
}

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)”
KindBehavior absent → bridge emits
featureevents[<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
rulePreserved in rules[<slug>] but not bound
criterionScenario emits when[] = [{ dispatch: "__todo_<id>" }], then{} = {}
fixtureLegacy fixture.payload used as scenario.given.state directly