Skip to content

ActionDescriptor configSchema: adopt an xExpression marker so designer forms render expression vs template semantics (objectui #2670 Phase 3 counterpart) #3304

Description

@os-zhuang

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.collection renders as an expression field.
  • 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' — an interpolate() 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 / map collectiontemplate (values are {leadList} / {items}).
  • start entry condition, decision branch expression / condition, edge guards, screen visibleWhenexpression.
  • script code 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 configSchema at all — use objectui's hardcoded flow-node-config.ts field 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. xExpression is ignored everywhere except the designer form generator.

Acceptance

  • With the engine running, the online loop config form renders collection identically to the offline hardcoded form (mono expression editor, {var} picker), with no false-positive brace-trap on {leadList}.
  • A genuine CEL predicate (start / decision condition) 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions