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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions