Skip to content

RFC-004 — field reference resolution

Status: ratified. Source: atomprd/specs/rfcs/RFC-004-field-ref-resolution.md.

Layer 2 fields use a $ref string to refer to runtime values. The resolution rules:

PrefixResolves to
state.<path>Project state slot.
item.<field>Inside a list section’s itemTemplate — the current iteration item’s field.
form.<field>Inside a form section — the live form input by name.
result.<path>Inside an action chain after a call step with assignTo — the result is bound.
entity.<entity_id>.<field>Static entity metadata (rare; mostly for codegen-time selectors).

Every $ref is statically resolved by the bridge:

  • state.habits → SenLang state binding state.habits (passed through unchanged).
  • item.name → SenLang $item.name (Sen’s iteration variable convention).
  • form.habitName → SenLang $form.habitName.
  • result.id → SenLang state.<assignTo path>.id after the assignment.
  • entity.<id>.<field> → resolved to the field’s literal type / metadata; doesn’t appear at runtime.

The validator checks every $ref against the project’s known state / form / item / result paths and rejects dangling refs at authoring time. The cloud authoring UI surfaces errors inline.

// In feature.behavior.steps[1] (a "call" step)
{ "kind": "call", "method": "POST", "endpoint": "/api/habits",
"body": { "name": { "$ref": "state.draftHabitName" } },
"assignTo": "state.lastCreated" }
// Later in the same chain
{ "kind": "navigate", "to": "detail",
"params": { "id": { "$ref": "result.id" } } } // result of the previous call
// In ui_view.behavior.screens.list.sections[1] (a "list" section)
{ "kind": "list", "source": "state.habits",
"itemTemplate": [
{ "kind": "text", "body": "{{ item.name }}" } // shorthand for { $ref: "item.name" }
] }

{{ <ref> }} inside a string is shorthand for { "$ref": "<ref>" }. The validator expands shorthand at parse time. Authors can use either; tools normalize to the explicit form on save.