Skip to content

Studio field designer is hand-coded, not metadata-driven — bring design-time authoring into the protocol (fieldForm / metadata-form-registry) #3257

Description

@os-zhuang

Context

Surfaced while shipping filtered roll-up summaries (#1868 → framework #3227 + objectui#2669). Adding one new field capability (summaryOperations.filter) required hand-writing a React editor in objectui (ObjectFieldInspector.tsxSummaryConfigFields) in lockstep with the spec change — which raised the question: shouldn't the field designer be generated from the protocol?

Observation

There is an asymmetry in how forms are produced:

  • Runtime business forms are metadata-driven. A showcase_expense_report record form (incl. its six summary fields) renders from Field metadata with zero hand-written render code. ✅ This is the ObjectStack way.
  • The design-time Studio field designer is not. objectui's object designer uses the bespoke packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx (~1400 lines of hand-coded React). Every field-type config there is hand-coded: select options, lookup picker + lookupFilters rows, summaryOperations (object/function/field/relationship), CEL rules — and now the summary filter row editor.

The protocol already declares a metadata form for a Field — packages/spec/src/data/field.form.ts (fieldForm), registered in packages/spec/src/system/metadata-form-registry.ts. But objectui does not consume metadata-form-registry / fieldForm at all (verified by grep); the Studio designer bypasses it. So fieldForm is effectively inert for the live authoring surface, and in it summaryOperations is a bare composite with no declared sub-fields.

Why this matters

A metadata-driven framework whose own primary authoring surface is not metadata-driven means every new authorable field capability costs a hand-coded editor in a separate repo (objectui), kept in lockstep with the spec by hand. That is exactly the kind of duplicated, drift-prone surface the platform elsewhere designs away. It also means the fieldForm protocol artifact is unenforced/inert (cf. the spirit of ADR-0078 "no silently-inert metadata" and ADR-0049 enforce-or-remove).

Why it's hand-coded today (the real constraint)

A declarative FormView can't yet express the interactions the field designer needs:

  • type-conditional sections (visibleWhen: data.type == 'summary') — this one FormView already supports;
  • a picker that live-loads the CHILD object's fields (the summary/lookup field pickers call useObjectFields(childObject));
  • CEL editors with schema-aware autocomplete (visibleWhen/readonlyWhen/requiredWhen);
  • a structured filter-row builder with per-child-field type coercion (boolean → true, numeric operator → { $gte: 500 }).

These require imperative React today, so objectui made the pragmatic split: bespoke design-time inspector, metadata-driven runtime forms.

Proposed direction (ADR-worthy)

Converge the design-time authoring surface onto the protocol, as a ratchet (not a big-bang rewrite):

  1. Extend the FormView/widget protocol so the design-time widgets are declarable: a filter-condition row editor, a dependent ref:object + child-field picker (scope a field list by a sibling value), a CEL predicate editor. Some of these widget names already appear in field.zod.ts's widget docs (filter-condition, object-ref, recipient-picker) but have no protocol-level form binding.
  2. Have objectui's Studio render the registered fieldForm (consume metadata-form-registry), starting with the simple field types, and keep the bespoke inspector only for the config blocks the protocol/renderer can't yet express — retiring them one at a time as the widgets land.
  3. Enforce-or-remove fieldForm: while it's inert, either wire it up (above) or mark it experimental, per ADR-0049/0078, so it isn't a false "the field editor is metadata-driven" signal.

Immediate small step (separate PR, will link)

Declare summaryOperations' sub-fields — object / function / field / relationshipField / filter — explicitly in fieldForm's composite (FormFieldSchema.fields already supports this), with filter bound to widget: 'filter-condition'. This makes the protocol path render structured fields instead of a raw JSON blob, and closes the gap that this issue's investigation found in fieldForm itself, without waiting on the larger convergence.

Affected

  • @objectstack/specfield.form.ts (fieldForm), system/metadata-form-registry.ts, the FormView/widget protocol in ui/view.zod.ts.
  • objectuiapp-shell metadata-admin ObjectFieldInspector, plugin-form / fields renderers (would need the new design-time widgets + to consume the registry).

Filed per AGENTS.md Prime Directive #10 (file out-of-scope findings rather than leave them buried). Found while runtime-verifying #1868.

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