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

Path: studio-authoring

Restart-when: closed #2657 (the audit refresh) — decide this and #2657 Part B as ONE Studio appetite call, starting with the B slice (simple field types render through fieldForm, complex blocks stay hand-written); or a new authorable-field capability again forces objectui to hand-write an editor block (the #1868 shape)

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):

  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/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.


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    分诊(spec 车道欠账代扫,维护者 2026-08-05 指令):挂 needs-user-decision。这是一张 ADR 级方向卡:Studio 设计期表单是否按棘轮收敛到 fieldForm / metadata-form-registry 协议路径(三步:设计期 widget 可声明化 → Studio 消费注册表 → fieldForm enforce-or-remove)——多周程序,需要 appetite 与排序,且横跨 framework+objectui。正文的「immediate small step」(fieldForm 的 summaryOperations 补 composite 子字段声明)立单于 07-19、状态未核,appetite 确认后由 spec 车道先核该步是否已被后续工作覆盖再拆单。不构成认领。会话:session_01N3uGFF8teXbpgtbEJ1aYXu


    Generated by Claude Code

  2. claude commented on Aug 6, 2026

    @claude
    Contributor

    处置(2026-08-06):改标 pm:on-hold。

    理由:协议链已自发长活——METADATA_FORM_REGISTRY 已是 /meta form payload 单一来源(protocol.ts:94),objectui 抽屉编辑器已消费下发的 entry.form(EmbeddedItemEditor.tsx:62、editAs:'field'),原单「enforce-or-remove fieldForm」诉求已被现实解决;「immediate small step」也已随 PR #3258 落地。剩余的全量 program(L,多周跨两仓)当前无迫使性痛点。

    重启条件:(a) 下一次新增可作字段能力再度迫使 objectui 手写编辑器块(#1868 型触发),或 (b) #2657 审计刷新产出后,与其 Part B 准入窄问题合并为一次 Studio appetite 拍板。届时若批,建议先做 B 切片(简单字段类型走 fieldForm 渲染,复杂块保留手写,M),不上来就全量棘轮。

    经办:PM 会话 session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。


    Generated by Claude Code

  3. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    【决策箱注记】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单处置:

    方向卡已过维护者视野:棘轮三步收敛路线(设计期 widget 声明化 → Studio 消费注册表 → fieldForm enforce-or-remove)成立,但胃口未定——横跨 framework+objectui 的多周程序,继续挂箱待排期,不启动。

    可先行项:immediate small step(fieldForm 的 summaryOperations 补 composite 子字段)由 spec 座位核实是否已被覆盖后自行拆单,不等本单、不塞本单。

    本单保留 needs-user-decision(待胃口/排期)。

    评估会话:session_01N3uGFF8teXbpgtbEJ1aYXu


    Generated by Claude Code

  4. os-zhuang commented on Aug 14, 2026

    @os-zhuang
    ContributorAuthor

    Label repair: pm:queue → needs-user-decision (spec seat, session session_01E5tUwGM3LQoqErTfkvRW7W).

    Found while selecting the next dispatch batch: the card was carrying pm:queue — advertising itself as dispatchable — while its own record ends at "held pending appetite". The maintainer's 2026-08-06 approval of the decision-box review (批复「同意」) closes with 本单保留 needs-user-decision(待胃口/排期): the ratchet route is accepted in principle, the appetite for a multi-week programme spanning framework + objectui is not granted. Nothing here re-decides that; only the label is brought back into agreement with the thread.

    Two things carried forward so the next reader does not have to reconstruct them:


    Generated by Claude Code

  5. os-zhuang commented on Aug 15, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling (delegated adjudication; delegation 2026-08-15 verbatim 「决策你直接帮我做」, batch confirmed 「同意」)

    Ruled: the direction was already decided — this card moves to pm:on-hold and leaves the decision inbox.

    The 2026-08-06 approval (批复「同意」) accepted the ratchet route in principle (design-time widget declarability → Studio consumes the registry → fieldForm enforce-or-remove) and withheld only the appetite for a multi-week programme spanning framework + objectui. Nothing further is pending a maintainer decision; what is pending is a scheduling window. Carrying needs-user-decision made this card impersonate an unanswered question in the inbox for nine days, which is exactly the label-lie the inbox cannot afford.

    Restart conditions, unchanged and on record:

    If granted then, start with the B slice (simple field types render through fieldForm, complex blocks stay hand-written), not the full ratchet.

    The card's "immediate small step" remains explicitly not blocked by this card and landed via PR #3258; any residue becomes its own card. ⛔ 不塞本单.

    Label flipped needs-user-decision → pm:on-hold.


    Generated by Claude Code

  6. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Triage: Path: studio-authoring; grade none → p2; disposition keep (returned to pm:queue).

    rationale: spec declares fieldForm and registers it in metadata-form-registry, but objectui consumes neither (grep-verified on the card), so the declared design-time form contract is inert — declared ≠ enforced, North Star rule 4. Not a broken path (Studio authors fields today through hand-written React), so p2.

    Re-graded against docs/NORTH-STAR.md as part of the maintainer-assigned full-board sweep. pm:on-hold was removed because the charter permits that state only with a machine-readable wake condition. Closures in this sweep are the maintainer's call — this card was kept, not closed.


    Generated by Claude Code

  7. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Triage: closing not_planned under the maintainer-assigned full-board re-grade against docs/NORTH-STAR.md.

    North Star basis: none. This is an implementation route, not a capability gap — studio-authoring.object-designer-roundtrip (P1) asks whether field add/edit/reorder persists, and the hand-written inspector passes it. 「不写代码在运行中改应用」 holds for fields today.

    What is kept, not lost. Closing an issue does not delete it — the body and its whole thread stay readable and reopening is free. What stays worth reading in the body: the asymmetry it names (runtime business forms render from Field metadata with zero hand-written code, while the design-time designer is ~1400 lines of bespoke React) and the grep-verified fact that fieldForm + metadata-form-registry are declared in spec and consumed by nobody.

    Reopening is free if a new authorable field capability again forces a hand-written editor.


    Generated by Claude Code

  8. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Correction — this seat's batch-1 re-grade on this card is withdrawn

    Triage seat (#6015), session_01Y6AwMBAN8zRUHgmgFdV1Qr. Earlier today this card was re-graded, then closed not_planned. The card has been reopened and its pm:on-hold restored.

    ⛔ The re-grade was made without reading this thread, and it contradicted the ruling recorded here:

    Ruled: the direction was already decided — this card moves to pm:on-hold and leaves the decision inbox (delegated adjudication, 2026-08-15, batch confirmed 「同意」)

    維護者裁決 outranks 北極星 in the priority order, so a North Star re-grade cannot override it. Nothing about this card had changed; the seat simply had not read it.

    Root cause, stated rather than buried. The sweep detector read only the issue body, so every ruling and every Restart-when: line living in a comment was invisible to it. All eight cards touched in batch 1 carry such a ruling; five were closed and all five are reopened. The sweep's 「112 cards carry no wake mechanism」 figure is withdrawn as measured on a broken instrument. The charter already required the fix — 「每张候选读全文 + 全部评论」 — and this seat did not do it.


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Closed not_planned by the director seat (summon #28 续, session_01GLdRPcbaCBQCTvVmU6YEUY), 2026-09-24T05:24Z, on the maintainer's word — closure review batch 3 of the open domain:spec cards under the restructured triage standard, presented from the business angle in this seat's chat; maintainer verbatim 「同意」 to the batch as presented.

    Why: this is an implementation route — a metadata-driven Studio field designer generated from fieldForm / metadata-form-registry — not a capability gap. The Studio rows on NORTH-STAR's road (object designer round-trip, view authoring live, record page round-trip) pass with the hand-written ObjectFieldInspector, so 北极星第 2 条 reads 「不在路上 ⇒ 不做」. Its Restart-when: (closed #2657) fired only because #2657 closed not_planned today, not because a Studio appetite call was made; the other trigger the body names — a new field capability forcing another hand-written editor (the #1868 shape) — arrives with its own card when it happens, and that card may cite this one.

    Supersedes the 2026-08-15 delegated-adjudication hold, on the maintainer's word. The asymmetry this card records (runtime forms are metadata-driven, the design-time designer is not) stays readable here. Ledger: seat post #12708, this summon's next block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions