Skip to content

Layer 2 schemas

Five atom kinds — feature, ui_view, rule, criterion, fixture — carry a structured Behavior layer in addition to free-form Intent. Codegen tools lower these directly into target output.

The Zod schemas live in @atomprd/specbehaviors.ts. Summaries below.

{
trigger?: string; // optional event hook ("onClick", "onSubmit", ...)
steps: Step[]; // required, may be []
onOk?: Step[]; // optional success-path tail
onErr?: Step[]; // optional error-path tail
}
type Step =
| { kind: "validate"; rule: string; errorMessage?: string }
| { kind: "call"; endpoint: string; method?: "GET"|"POST"|"PUT"|"DELETE"|"PATCH"; body?: unknown; assignTo?: string }
| { kind: "dispatch"; feature: string; args?: Record<string, unknown> }
| { kind: "setState"; path: string; value: unknown }
| { kind: "navigate"; to: string; params?: Record<string, unknown> }
| { kind: "log"; level: "debug"|"info"|"warn"|"error"; msg: string }
| { kind: "if"; condition: string; then: Step[]; else?: Step[] }

Lowered by @atomprd/codegen-senlang into a SenLang Action chain — one $do: ... per step. See Codegen → SenLang bridge.

{
rootScreen: string; // ID of the entry screen
screens: {
[id: string]: {
sections: Section[]; // ordered top-to-bottom
};
};
}
type Section =
| { kind: "heading"; level: 1|2|3; text: string }
| { kind: "text"; body: string }
| { kind: "list"; source: string; itemTemplate: Section[] }
| { kind: "form"; fields: { name: string; label?: string; type: "text"|"number"|"date"|"select"; options?: string[] }[]; submitLabel?: string; submitDispatch?: string }
| { kind: "detail"; source: string; rows: { label: string; value: string }[] }
| { kind: "button"; label: string; dispatch: string; variant?: "primary"|"secondary"|"danger" }
| { kind: "input"; name: string; bind: string; placeholder?: string }
| { kind: "switch"; bind: string; label: string };

Lowered into a SenLang view tree. Multi-screen views compile to a top-level $Switch over a state slot.

{
enforced_at: EnforcedAt[];
}
type EnforcedAt =
| { kind: "ui_disabled"; ui_view: string; binding: "submitButton" | string }
| { kind: "pre_call"; feature: string; on_failure: "abort" | "warn" }
| { kind: "validator"; field: string; entity: string };

Lowered into SenLang rules[<slug>] plus bindings on the matching button / call site.

{
before_after: {
state_diff: Record<string, { before: unknown; after: unknown }>;
api_calls: ApiCall[];
derived_diff?: Record<string, unknown>;
};
}
type ApiCall = {
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
endpoint: string;
body?: unknown;
response?: unknown;
};

Lowered into RFC-0010 scenario fixtures with given.state (the before snapshot), when array (the api_calls), and then block (the after snapshot).

{
shape: "scenario" | "snapshot" | "stub";
before?: Record<string, unknown>;
after?: Record<string, unknown>;
api_calls?: ApiCall[];
}

Lowered into a sibling scenario file. When shape: "stub", the bridge emits a TODO scaffold.

Tools encountering Layer 2 fields they don’t understand MUST preserve them as opaque jsonb. v0.4+ may extend Step / Section / EnforcedAt unions; existing tools should ignore unknown variants rather than reject.