Skip to content

feat(metadata-admin): variable data-picker for flow expression/template fields (#1934) - #1973

Merged
os-zhuang merged 1 commit into
mainfrom
feat/flow-variable-picker
Jun 24, 2026
Merged

feat(metadata-admin): variable data-picker for flow expression/template fields (#1934)#1973
os-zhuang merged 1 commit into
mainfrom
feat/flow-variable-picker

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes#1934.

What

Business users authored CEL expressions and {var} templates in the Studio flow builder by typing reference names from memory — there was no way to discover or insert the references actually in scope. This adds a {x} data-picker (like Power Automate "Dynamic content" / Salesforce's resource picker) to every expression / template config surface.

A trigger button on the field opens a searchable, grouped popover of the references in scope at that node, and picking one inserts the correctly-braced token at the cursor.

In scope (graph-aware)

Resolved by walking the flow graph back from the current node, so only what's reachable is offered:

  • Flow variablesdraft.variables[].
  • Upstream outputs — each ancestor node's outputVariable(s) / collected screen fields[].name / assignments keys / idVariable. A node's own and any downstream output is excluded — a downstream output is never offered upstream.
  • Loop / map item — the enclosing iteratorVariable.
  • Trigger record — on a record-triggered flow, the start node's objectName schema is fetched and expanded: bare <field> on the start node's own entry condition, record.<field> (+ record / previous) downstream — matching how the showcase flows and the engine reference the record.

Brace handling (ADR-0032)

Insertion handles the recurring {record.x} brace-in-CEL trap for the author:

  • expression fields → bare CEL (record.name)
  • template fields (text / textarea / key-value values) → {record.company}

Free-text typing is untouched, and an empty scope degrades to a plain input.

Surfaces

decision Branches (expression), edge Condition, Assignment / CRUD / subflow values, screen description, and http fields — via one shared VariableTextInput.

Code

  • inspectors/flow-scope.ts — pure, framework-free graph-walk + ref resolution (unit-tested).
  • inspectors/useFlowScope.ts — React adapter; lazily fetches + expands the trigger object's fields.
  • inspectors/VariableTextInput.tsx — input/textarea + cmdk popover; cursor insertion + brace rule.
  • Wiring in FlowNodeConfigField, FlowNodeInspector, FlowObjectListField, FlowKeyValueField, FlowEdgeInspector.

Tests / verification

  • flow-scope.test.ts — 24 unit tests for the scope-resolution graph-walk (ancestors, downstream-not-upstream, trigger prefix, de-dup, loop iterator, non-record trigger).
  • pnpm build (tsc) clean; full metadata-admin suite green (442 tests).
  • Browser-verified against the CRM showcase lead_qualification_conversion flow: picker lists flow variables + record.<field> (from crm_lead); bare insertion in the decision Branches expression and {record.company} in the http URL field; graph-aware trigger scoping.

🤖 Generated with Claude Code

…te fields (#1934)
Business users authored CEL expressions and {var} templates in the flow builder
by typing reference names from memory — there was no way to discover or insert
the references actually in scope. Add a "{x}" data-picker (like Power Automate
"Dynamic content" / Salesforce resource picker) to every expression / template
config surface.
- flow-scope.ts: pure, GRAPH-AWARE scope resolution at a node — flow variables,
upstream node outputs (ancestors only, found by walking edges back), enclosing
loop/map iterators, and the trigger record. Trigger fields are bare on the
start node's own entry condition and record.<field> downstream (matching the
showcase flows / engine context). A node's own and downstream outputs are
excluded, so a downstream output is never offered upstream.
- useFlowScope.ts: React adapter that lazily fetches the trigger object's field
catalog and expands record.<field> refs, grouped for the picker.
- VariableTextInput.tsx: input / textarea + searchable, grouped popover (cmdk).
Inserts the correctly-braced token at the cursor — bare CEL in `expression`
fields, `{var}` in template (text / textarea / value) fields — handling the
ADR-0032 brace-in-CEL trap for the author. Free-text typing is untouched and
an empty scope degrades to a plain input.
- Wired into decision Branches, edge Condition, assignment / CRUD / subflow
values, screen description, and http fields.
- Unit tests for the scope-resolution graph-walk.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@os-zhuang
os-zhuang merged commit 41c60c4 into mainJun 24, 2026
10 checks passed
@os-zhuang
os-zhuang deleted the feat/flow-variable-picker branch June 24, 2026 15:05
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)58.8 KB350 KB
Entry fileindex-KXeSOUVM.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)6.59KB2.34KB
app-shell (runtime-config.js)4.72KB1.69KB
app-shell (types.js)0.01KB0.04KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)17.16KB3.44KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)4.79KB1.88KB
auth (LoginForm.js)5.11KB1.92KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)24.74KB6.00KB
auth (createAuthenticatedFetch.js)3.33KB1.32KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.01KB0.79KB
auth (useIsWorkspaceAdmin.js)1.28KB0.70KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)418.42KB89.09KB
core (index.js)1.52KB0.55KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)99.69KB24.89KB
fields (index.js)166.21KB40.08KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)2.68KB0.98KB
layout (index.js)36.30KB10.04KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)5.09KB1.84KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.46KB1.03KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.50KB0.70KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.98KB12.65KB
plugin-charts (index.js)45.46KB12.78KB
plugin-chatbot (index.js)153.41KB37.19KB
plugin-dashboard (index.js)107.36KB26.65KB
plugin-designer (index.js)213.42KB42.94KB
plugin-detail (index.js)195.59KB47.19KB
plugin-editor (index.js)2.38KB1.06KB
plugin-form (index.js)93.23KB22.71KB
plugin-gantt (index.js)133.11KB32.92KB
plugin-grid (index.js)115.41KB31.01KB
plugin-kanban (index.js)48.82KB13.13KB
plugin-list (index.js)94.53KB22.32KB
plugin-map (index.js)16.02KB4.98KB
plugin-markdown (index.js)13.58KB4.64KB
plugin-report (index.js)37.83KB9.97KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-tree (index.js)8.04KB2.70KB
plugin-view (index.js)84.45KB20.36KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)18.23KB5.97KB
react (index.js)0.76KB0.42KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

os-zhuang added a commit that referenced this pull request Jun 24, 2026
…array assignments + script bare mode (#1934) (#1975)
Follow-ups to the variable data-picker (#1973):
- Scope-aware "unknown reference" warning — pairs the picker with the inline
validation the issue asked for: flags a typed reference whose ROOT isn't in
scope at the node, with a nearest-match "did you mean?" hint. Deliberately
conservative to avoid false positives (root-only so `record.<anything>` is
fine when `record` is in scope; skips function/macro calls, string literals,
CEL keywords and runtime globals like `env` / `$error`; typo hint only within
edit distance 2). Non-blocking amber; the ADR-0032 brace error still wins.
CEL for expression fields, `{…}` holes for templates. New pure `flow-ref-check`
module with unit tests.
- Assignment values authored in the array form `[{ variable, value }]` now
render in the key/value editor (and so get the picker) instead of falling back
to the Advanced JSON block. The editor reads BOTH the object-map and array
shapes and preserves whichever was authored. Round-trip unit tests.
- A script `code` body (JS/TS, not a `{var}` template) now inserts BARE
references via a new `refMode` field-schema override — `{x}` is a syntax error
in a script. Fixes a brace-mode regression from #1973 where every textarea was
treated as a template.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jun 24, 2026
…previous.<field> picker refs (#1934) (#1978)
Follow-ups to the data-picker (#1973) and its inline validation (#1975):
- Surface the brace (ADR-0032) + scope-aware "unknown reference" validation on
the repeater surfaces that carry the picker but previously had NO inline check:
decision Branches expressions and screen field "visible when" (objectList
expression columns) and assignment / CRUD / subflow key-value values.
Extracted a shared <FlowExprIssue> line so all four surfaces validate
identically — CEL brace + scope for expression columns, {…}-hole scope for
template values (single-brace is legal in a template, so it is never
brace-flagged).
- The trigger-record picker now offers `previous.<field>` references (not just
the whole `previous`) on update / change / before-update triggers.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flow builder: variable data-picker (autocomplete) for expression / template fields

1 participant

@os-zhuang