feat(automation): xExpression marker on the loop collection config field (#3304)#3313
Merged
Merged
Conversation
…eld (#3304) The flow designer generates a node's config form from its published `configSchema` (ADR-0018). Introduce an `xExpression: 'expression' | 'template'` marker on string properties — riding the same Zod `.meta()` → JSON-Schema channel as `xRef` / `xEnumDeprecated` — that declares whether a string is bare CEL or an `interpolate()` single-brace `{var}` template. Apply it to the `loop` node's `collection` (a `{tasks}` template): • LoopConfigSchema.collection — the canonical Zod source (control-flow.zod.ts), emitted via z.toJSONSchema. • the shipped descriptor's configSchema literal (service-automation loop-node.ts) — the JSON objectui actually reads. loop's configSchema is a hand-written literal that doesn't derive from the Zod schema, so both are annotated so they agree. Closes the live divergence: without the marker the designer rendered `collection` as plain text online while the offline hardcoded form rendered it as a mono expression editor, and the CEL brace-trap false-flagged `{tasks}`. objectui #2670 Phase 3 (#2699) already consumes the marker. Additive + backward-compatible: an unknown value is ignored, runtime unchanged. Follow-up (tracked in #3304): the same marker on map/decision/script and the node types that publish no configSchema yet. Tests: LoopConfigSchema emits xExpression through z.toJSONSchema (spec); the registered loop descriptor's configSchema.collection carries it (service-automation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 105 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 19, 2026 18:07
os-zhuang
added a commit
that referenced
this pull request
Jul 20, 2026
…s (designer parity, #3304) (#3327) assignment, create/update/delete/get_record, and screen now publish configSchemas mirroring objectui's hardcoded field groups — free-form maps as JSON-Schema open objects (additionalProperties: true) rendered by the designer's keyValue editor (objectui #2708), object references as xRef, screen's visibleWhen as an xExpression CEL column. decision/wait/script/subflow stay deliberately schemaless (documented + tested — a partial schema would drop editors). Verified: parity tests, tsc, 335-test suite green. Descriptor metadata only; no runtime behavior change. Closes the #3304 implementation series: loop (#3313), map (#3321), objectui adapter (#2708), this PR.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First increment of #3304 — the descriptor-side counterpart to objectui #2670 Phase 3 (merged as objectui #2699).
What
Introduce an
xExpression: 'expression' | 'template'marker on flow-node config string properties, riding the exact same Zod.meta()→ JSON-Schema channel as the existingxRef/xEnumDeprecatedmarkers. It tells the flow designer whether a string is bare CEL or aninterpolate()single-brace{var}template, so the designer renders the right editor (mono +{var}picker, and whether the CEL brace-trap applies) instead of guessing from the field name.Apply it to the one field where it closes a live divergence today: the
loopnode'scollection(a{tasks}template).Why this scope
There are two separate channels that produce a node's published
configSchema:z.toJSONSchema— onlyapproval(getApprovalNodeConfigJsonSchema)..meta()flows through here verbatim.service-automation/builtin/*.ts—loop/parallel/try_catch/http/connector_action/notify. These ignore their Zod schemas.A further 11 node types publish no
configSchemaat all (decision,assignment, CRUD×4,screen,script,wait,subflow,map).Among every node that already ships a
configSchema,loop.collectionis the only template/expression field — and it's exactly the divergence #3304 leads with: online the designer rendered it as plain text while the offline hardcoded form rendered a mono expression editor, and the CEL brace-trap false-flagged{tasks}as a malformed condition. (Marking other config-schema string fields such ashttp.urlwould create a mono-vs-text divergence, since objectui renders those as plain text — so they are deliberately left unmarked.)Changes
packages/spec/src/automation/control-flow.zod.ts—LoopConfigSchema.collectiongains.meta({ xExpression: 'template' })(canonical source; emitted viaz.toJSONSchema).packages/services/service-automation/src/builtin/loop-node.ts— the shipped descriptor'sconfigSchemaliteral (the JSON objectui actually reads) gainsxExpression: 'template'oncollection. loop's literal doesn't derive from Zod, so both are annotated so they agree.Follow-up (tracked in #3304, not this PR)
The same marker on
map.collection(template),decision.expression/start.condition(expression),scriptbody (expression + multiline) — each lives on a node type that publishes noconfigSchemayet, so each needs a new config schema authored (using objectui's hardcodedflow-node-configas the spec of record) plus a ratchet-manifest update. Kept out of this PR to land the concrete fix cleanly.Verification
LoopConfigSchemaemitsxExpression: 'template'(withdescriptionpreserved) throughz.toJSONSchema(control-flow.test.ts); the registeredloopdescriptor'sconfigSchema.collectioncarries the marker (loop-node.test.ts). Both green.tsc --noEmitclean for@objectstack/specand@objectstack/service-automation;pnpm gen:schemasucceeds with no ratchet-manifest drift (annotating an existing schema doesn't change schema keys).xExpressionvalue is ignored by the designer, runtime behavior unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi
Generated by Claude Code