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.tsx → SummaryConfigFields) 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):
- 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.
- 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.
- 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/spec — field.form.ts (fieldForm), system/metadata-form-registry.ts, the FormView/widget protocol in ui/view.zod.ts.
objectui — app-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.
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.tsx→SummaryConfigFields) 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:
showcase_expense_reportrecord form (incl. its six summary fields) renders from Field metadata with zero hand-written render code. ✅ This is the ObjectStack way.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 +lookupFiltersrows,summaryOperations(object/function/field/relationship), CEL rules — and now the summaryfilterrow editor.The protocol already declares a metadata form for a Field —
packages/spec/src/data/field.form.ts(fieldForm), registered inpackages/spec/src/system/metadata-form-registry.ts. But objectui does not consumemetadata-form-registry/fieldFormat all (verified by grep); the Studio designer bypasses it. SofieldFormis effectively inert for the live authoring surface, and in itsummaryOperationsis a barecompositewith 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
fieldFormprotocol 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
FormViewcan't yet express the interactions the field designer needs:visibleWhen: data.type == 'summary') — this one FormView already supports;useObjectFields(childObject));visibleWhen/readonlyWhen/requiredWhen);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):
filter-conditionrow editor, a dependentref:object+ child-field picker (scope a field list by a sibling value), a CEL predicate editor. Some of these widget names already appear infield.zod.ts'swidgetdocs (filter-condition,object-ref,recipient-picker) but have no protocol-level form binding.fieldForm(consumemetadata-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.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 infieldForm'scomposite(FormFieldSchema.fieldsalready supports this), withfilterbound towidget: '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 infieldFormitself, without waiting on the larger convergence.Affected
@objectstack/spec—field.form.ts(fieldForm),system/metadata-form-registry.ts, theFormView/widget protocol inui/view.zod.ts.objectui—app-shellmetadata-adminObjectFieldInspector,plugin-form/fieldsrenderers (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.