Repository navigation
Description
Background (ADR-0018)
The flow designer's node config forms are generated server-first from each node type's published ActionDescriptor.configSchema (the JSON Schema compiled from the executor's Zod), falling back to a hardcoded client form only when no schema is published. objectui already consumes UI hints carried on schema properties via the Zod .meta() channel — xRef (typed reference picker) and xEnumDeprecated (drop a deprecated enum spelling).
There is currently no way for a schema to say whether a string property is a bare-CEL expression or an interpolate() {var} template. So the adapter maps every string to plain text, dropping the mono editor, the data-picker brace mode, and — critically — the correct validation. Now that the engine publishes a loop configSchema, this has become a live split:
- Offline (hardcoded form):
loop.collectionrenders as anexpressionfield. - Online (schema→adapter): the same field renders as plain
text.
And before the objectui-side gating fix, the offline expression rendering also false-positived the CEL brace-trap on the legal {leadList} template. objectui #2670 Phase 3 (PR incoming) fixes the consumer side and defines the contract below; this issue is the producer side — teach the descriptors to emit the marker so the online form matches.
Contract: xExpression
A string property MAY carry xExpression: 'expression' | 'template' (Zod .meta({ xExpression }), same channel as xRef):
'expression'— bare CEL (a predicate / value expression). Mono editor, predicate validation (ADR-0032 brace-trap applies), data-picker inserts bare refs.'template'— aninterpolate()single-brace{var}template. Mono editor, data-picker inserts{var}, no predicate validation (its{…}is legal; only a soft unknown-ref check applies).format: 'multiline'combines:expression + multiline→ textarea in bare-ref (script-body) mode;template + multiline→ textarea in template mode.- Unknown value degrades to plain text (forward-compatible).
- Precedence in the adapter:
xRef > enum > xExpression > scalar.
The objectui adapter mapping (the consumer spec) lives in packages/app-shell/src/views/metadata-admin/inspectors/json-schema-to-fields.ts (scalarField / exprModeOf), with the rendering semantics in FlowNodeConfigField and the canvas scan in flow-expr-problems.ts.
Work items
Annotate the executor Zod schemas so z.toJSONSchema emits xExpression:
-
loop/mapcollection→template(values are{leadList}/{items}). -
startentrycondition,decisionbranchexpression/condition, edge guards, screenvisibleWhen→expression. -
scriptcode body →expression+multiline(bare refs;{x}is illegal in JS/TS). - Audit connector / plugin descriptors for expression/template string fields and mark them.
- Fill the ~11 node types that publish no
configSchemaat all — use objectui's hardcodedflow-node-config.tsfield groups as the spec of record, so online and offline forms converge.
Non-goals
- No change to engine/runtime behavior — this is designer metadata only.
xExpressionis ignored everywhere except the designer form generator.
Acceptance
- With the engine running, the online
loopconfig form renderscollectionidentically to the offline hardcoded form (mono expression editor,{var}picker), with no false-positive brace-trap on{leadList}. - A genuine CEL predicate (
start/decisioncondition) still validates.
Current evidence
The online (schema→text) and offline (hardcoded→expression) loop.collection forms already diverge today; that divergence is what this marker closes.
Ref: objectui #2670 (Phase 3).
Activity
feat(flow-designer): #2670 Phase 3 — nested container node selection …
feat(automation): xExpression marker on the loop collection config fi…
feat(automation): publish a configSchema for the map node (designer p…
feat(automation): publish configSchemas for the keyValue-capable node…
os-zhuang commentedon Jul 20, 2026
Complete. Final map of the implementation series:
loop,mapxExpressionmarkers — #3313, #3321assignment,create_record/update_record/delete_record/get_record,screenadditionalProperties: trueshape forfields/filter/assignments/defaults) — #3327xExpressionmapping + validation gating; objectstack-ai/objectui#2708: keyValue + numberList schema mappingdecision,wait,script,subflowwaitEventConfig, script's actionType-conditional form, subflow's top-leveltimeoutMs). Asserted inconfig-schemas.test.tsso a partial schema can't slip in silently.Every node where server-driven parity is achievable without regression now has it; the rest are documented, tested design decisions. Value schemas for the maps are deliberately permissive (
true) — real metadata carries operator objects ({"$ne": null}),{var}templates, and non-string literals.Possible follow-ups (not gaps, filed as future work): a generic conditional-visibility marker (e.g.
xShowWhen) would unlockscript; a cross-repo conformance fixture running published schemas through the documented mapping table would harden the descriptor↔adapter contract.Generated by Claude Code
docs(releases): complete the v16 Console section from objectui's own …
fix(automation): put a screen field's `visibleWhen` on the wire (#3528)…
fix(automation,lint,spec): validate the expression slots a node's `co…