Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[P3] SDUI: render ActionParamDialog params through the shared form field-widget renderer (retire bespoke per-type branches) #2700

Closed

Description

@os-zhuang

Goal

Stop hand-rolling ActionParamDialog's per-type rendering. Route each action param through the same field-widget renderer the object form uses (fieldWidgetMap / FORM_FIELD_TYPES in @object-ui/fields), so a declared action param of any field type — file, image, richtext, markdown, color, address, … — renders its real widget for free, and the param surface can never silently drift behind the form surface again.

This is the architectural generalization of #2698 (which adds a single file branch by hand). #2698 fixes the immediate approvals need; this issue removes the whole class of "param type X falls through to a text box" bugs.

Why

ActionParamDialog is a bespoke, hand-rolled form, not routed through any shared field-component registry. Its rendering is a manual ternary chain over param.type:

  • select → Select
  • lookup/reference (with referenceTo) → LookupField (the only @object-ui/fields widget it reuses)
  • textarea → Textarea
  • number → number Input
  • everything else → a plain text Input fallback

See packages/app-shell/src/views/ActionParamDialog.tsx (render branches ~L195–270; it imports only LookupField from @object-ui/fields at L36). So every rich type a designer might declare — file, image, richtext, markdown, color, address, geolocation, code, … — collapses to a text box. #2698 is about to add file as branch #6; without this refactor, the next type is another one-off branch, and the param surface keeps trailing the form surface.

Meanwhile the object form already resolves every one of these via fieldWidgetMap (packages/fields/src/index.tsx:2138, keys frozen as FORM_FIELD_TYPES at L2223; 'file' → FileField at ~L2174, markdown/html/richtext → RichTextField, etc.), lazy-loaded and registered through ComponentRegistry (createFieldRenderer at L2078, mapFieldTypeToFormType at L1835). The inline-grid dispatcher FieldEditWidget deliberately excludes the heavy/binary types (INLINE_EXCLUDED_FIELD_TYPES — file, image, richtext, …), so that path is not the one to reuse; the form path is.

Proposed design (B)

Replace the bespoke branch chain with a param→field adapter that delegates to the form field-widget renderer:

  1. paramToField(param) — a small pure adapter mapping an ActionParamDef to the { name, type, ...config } field shape the form widgets consume. It already exists in spirit: resolveActionParams() copies lookup config from the underlying object-field; extend that to carry the widget-relevant config for all types (multiple, accept/maxSize for file, options for select, etc.). Mirror filterVisibleParams' pure, testable style.
  2. Render via the shared renderer — for any param.type present in FORM_FIELD_TYPES, render the form widget ({ value, onChange, field }) instead of a hand-written control. Keep the current bespoke branches only as fallbacks for param-only shapes with no field widget.
  3. Preserve param semantics — required validation (isMissingValue already treats []/empty as missing), visible CEL gating (usePredicateScope + ExpressionEvaluator), helpText, and error styling must survive the swap unchanged.
  4. No adapter threading — the ambient UploadProvider and SchemaRendererContext (dataSource for relational widgets) are already in scope where ActionParamDialog mounts, exactly as the LookupField reuse relies on today.

Considerations / risks (why this is bigger than #2698)

  • Param model ≠ field model. ActionParamDef (@object-ui/core) is not a field def; the adapter must be exhaustive and lossless, and spec may need to widen ActionParamSchema for widget config.
  • Relational widgets need context. lookup/master_detail/user read dataSource from SchemaRendererContext; confirm every host of ActionParamDialog (ObjectView / RecordDetailView / DeclaredActionsBar / approvals inbox) provides it.
  • Value-shape parity. Widgets emit their own value shapes (e.g. file → string | string[], date → ISO). Confirm each round-trips to what the action runner/endpoint expects; add coverage.
  • Regression surface. This rewrites the render path for every existing param type, so it needs a broad snapshot/behavior test net (select, lookup, textarea, number, boolean, date) before flipping, not just the new types.
  • Bundle. fieldWidgetMap is lazy; keep the dialog's widgets lazy so a param dialog doesn't pull every widget eagerly.

Scope / acceptance

  • ActionParamDialog renders params through the shared form field-widget renderer; bespoke per-type branches removed (or reduced to genuine param-only fallbacks).
  • Every FORM_FIELD_TYPES type usable as a param renders its real widget (spot-check file, image, richtext, color, date, select, lookup).
  • required + visible (CEL) + multiple + value round-trip preserved for all types; a drift test asserts param support ⊇ form support so the two can't silently diverge again (mirrors the FieldEditWidget ↔ FORM_FIELD_TYPES drift test).
  • No adapter threading — ambient UploadProvider / SchemaRendererContext.
  • app-shell suite green; console typecheck + lint clean.

Relationship to #2698

Do #2698 first (it lands the file branch + spec type: 'file' + tests, unblocking the approvals composer retirement). This issue then generalizes that one branch into "all types via the shared renderer" and deletes the hand-written branches. If prioritized, #2698's file branch is the seed of paramToField.

Refs

objectui#2698 (P2 — the single file branch; approvals need) · objectui#2697 (declared-action / DeclaredActionsBar) · packages/fields/src/index.tsx (fieldWidgetMap / FORM_FIELD_TYPES / mapFieldTypeToFormType) · packages/fields/src/FieldEditWidget.tsx (INLINE_EXCLUDED_FIELD_TYPES — why the inline path is the wrong one to reuse) · packages/app-shell/src/views/ActionParamDialog.tsx (the bespoke branch chain being retired)

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