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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions