fix(plugin-form,types): the default form draws a self-describing inline section entry, and ObjectFormSection.fields gains the form view's { field } arm (objectui#11615) - #11622
Conversation
…ne section entry, and `ObjectFormSection.fields` gains the form view's `{ field }` arm
The pooled branch of `buildSectionFields` dropped every section entry its
parent field pool did not hold, the self-describing inline `FormField` too,
so a section drew that entry on the five pool-less arms and skipped it on
the default (`simple`) one. It now draws an entry `isInlineFieldDef`
accepts as the pool-less branch does; name-only entries keep the
objectui#9884 intersection and its warning, which no longer names a drawn
inline member.
`SimpleObjectForm` reads the shared `hasInlineFieldSource` for its submit
carve-out, and with no adapter treats fully-inline sections as a
members-only field source, so that collector opens on `initialValues` and
its `onSuccess` is the write, as on the five other layouts.
`ObjectFormSection.fields` declares the spec's `FormFieldInput`, by
reference, beside a name and an inline `FormField`.
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…ebt ledger Its `fields` member now references `@objectstack/spec`'s `FormFieldInput` (objectui#11615), so its "Aligns with @objectstack/spec FormSection" claim has a compile-time tie and `check:spec-symbols` reports the ledger entry as stale. Removed as the gate prescribes; `--claim-ledger` prints the same block. Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
… entry shapes as `ObjectFormSection.fields`
`ObjectForm` hands an authored section's `fields` to the tabbed, wizard,
split, drawer and modal configs verbatim, and each of those layouts draws a
`{ field, … }` entry through `buildSectionFields`. Their config types
declared `(string | FormField)[]`, so once `ObjectFormSection.fields` names
the form view's entry (objectui#11615) the handover no longer compiled. Each
now reads `NonNullable<ObjectFormSection['fields']>`, by reference.
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…t widens Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
CI red on The cause is this PR's widening of A patch round is underway on the same claim and branch:
The PR stays a draft. The contract review that was judging
Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgmentsRead from the net diff against
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
… `sectionEntryName`, respell the app-shell reader
`ObjectFormSection.fields` gained the form view's `{ field }` arm
(objectui#11615), so an app-shell test that named every object entry the
section-group resolver returns by `.name` stopped compiling (TS2339; the read
gave `undefined` on a `{ field }` entry all along). The form package already
spells the identity rule once, `sectionEntryName`; it is now published beside
`resolveSectionGroupReferences`, whose result it reads, and the test names
each entry through it. No cast, no second predicate.
Also: the changeset names that reader-side break and its remedy, the README
and docs page say how to read an entry, and the `object-form` element gate's
docblock no longer claims its `customFields`-only exemption is in step with
the components (comment only; the gate is unchanged).
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsRead from the net diff against
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
The seat now marks this ready and arms auto-merge through the merge queue.
Generated by Claude Code |
Fixes #11615
Clause-②: yes
What this does
The default (
simple)object-formnow draws a self-describing inline section entry the same way thetabbed,wizard,split,drawerandmodalforms already did.ObjectFormSection.fieldsnow declares the form view's{ field, … }entry. This carries out triage's first grade as ruled (comment5980751840, Option 1).What it accepts now, and the type gain (Clause-② widening)
@object-ui/types.ObjectFormSection.fieldsis(string | SpecFormFieldInput | FormField)[].@objectstack/spec'sFormFieldInput, by reference. It is the authoring face ofFormFieldSchema, the entry that the spec'sFormSectionSchema.fieldstakes beside a bare name.fieldsentry is stillz.any()there.ObjectFormSection.fields(stillminor, as the changeset states).typeof entry === 'string' ? entry : entry.nameno longer compiles, because the form view's{ field }entry has noname. Before the gain, that read returnedundefinedon such an entry without a type error.sectionEntryName. Patch round 1 (31a9d9a) publishes it from the@object-ui/plugin-formentry, besideresolveSectionGroupReferences, whose result it reads. That is a furtherClause-②widening, named in the changeset.@object-ui/plugin-formtypes. The five layout section configs now take NonNullable ofObjectFormSection['fields'], by reference:FormSectionConfig(tabbed),WizardStepConfig,SplitFormSectionConfig,DrawerFormSectionConfigandModalFormSectionConfig.ObjectFormpasses an authored section'sfieldsto these configs unchanged, so without it the ruled type gain does not compile:tscreported TS2322 at each of the five placesObjectFormpasses them.{ field }entry throughbuildSectionFields. The only alternative was a cast, which would hide what these layouts draw.@object-ui/plugin-formruntime,simpleform only. Three changes:buildSectionFieldswith a field pool now draws any entry thatisInlineFieldDefaccepts, whatever the pool holds, and draws it as written. The branch with no pool gives the same result.isInlineFieldDefaccepts an object whosefieldis not a string and whosenameis a string.hasInlineFieldSource. Before, it usedhasInlineFields, which is true only for a non-emptycustomFields.initialValues/initialData, as the five other layouts do.What stays refused or warned
{ field }entry still resolve against the field pool onsimple(the objectui#9884 intersection offieldsandsections). If the pool lacks the name, the entry is dropped, and a warning is logged once when the object declares the field.nameis malformed. A form with a field pool still does not draw it.submitHandlerand no data source, the submit still refuses withDataSource is required for form submission (inline mode not configured)unless every section entry is inline. One name among inline entries still refuses, and this is now pinned on all six layouts.object-formelement gate is unchanged. See Acceptance notes.Behaviour change for an existing schema. On
simple, take an inline entry whose name the object declares but top-levelfieldsleaves out. Before, it was dropped, with the intersection warning. Now it is drawn as its own definition, with no warning, as on the other five layouts. With a data source, its value is written as usual. As on every form, a value for a field the object does not declare is stripped from the write.Zone 2 item 2: the submit refusal, measured
Measured with a throwaway probe (not committed) and the ablations below. The form is
simplewith all-inline sections, nocustomFields, no data source andinitialValues: { ref: 'SEED' }:refopens as9db9ff3onErrorwith the refusalrefonErrorwith the refusal, after collecting a valueref''onSuccessref'SEED'onSuccesswith the collected valuestabbed/draweron base, the same form (probe)ref'SEED'onSuccesswith the collected valuesDecision:
simplenow counts all-inline sections as an inline field source, usinghasInlineFieldSource. The code and the family precedent decide this:handleSubmitjustified thesimple-only exception with one premise: "sections only SELECT pooled fields, so a sections-only form with no adapter resolves zero fields". This card removes that premise. With the drawing change alone, the form draws and collects its field and then refuses, while the five other layouts accept the same form. That would be a second contract that onlysimplefollows.simpleincluded.object-formelement gate already useshasInlineFieldSourceforrequiresObject, for every form type.{ field }entry means the form needed metadata it could not get, and it refuses.The boundary case, before and after.
submitTargetRefusal.test.tsxchanges as follows:formType simple: BOUNDARY — sections of inline fields are NOT its field sourcepinned the refusal of a form that drew nothing.the inline-sections collector opens on initialValues and submits what it holds.simplealso joins the six-layout rowBOUNDARY — one bare field name among inline ones still refuses. The inline-sections rowsections of inline runtime fields — the README's own shape — still worknow runs all six layouts.Zone 2 item 4: "carries its own
type" is not the right test for "self-contained"fieldis not a string. That path does not checktype.nameand leavestypeoptional ("the renderer's default input when omitted"). That arm isobjectFormRuntimeFieldin objectstack'scomponent.zod.ts, landed by feat(spec)!: object-form customFields takes a closed runtime form field, and both forms' sections a page-block section shape (#21464, S-forms) objectstack#21742.isInlineFieldDefinsubmitTarget.ts, which the submit-target rule reads. This PR exports it and reuses it, so one predicate answers both "needs no adapter" and "needs no pool".{ name, label }entry with notypedraws the default input on all six layouts. This was measured, and the new pin covers it.Testing for
typewould have kept skipping a spec-legal typeless inline entry onsimplewhile the other five layouts draw it. That is the divergence this card closes, and it would also have added a second predicate.Zone 2 item 3: no maintainer ruling forbids drawing
object-master-detail-form.fields声明「Ignored whensectionsis given」,而运行时把两者取交集 —— 落在fields之外的 section 成员静默消失,是最后一个时整段一起消失 #9884 landed limits the intersection to members the object declares.5969880008). It keeps the inline section entry, with "No narrowing that refuses the documented wizard".File surface: what the claim did not list, and why
The claim covers
sectionFields.ts,ObjectForm.tsx,submitTargetRefusal.test.tsxand the tests beside it,ObjectFormSection.fieldsand the changeset. The PR also touches these files:submitTarget.ts: exportsisInlineFieldDef(Zone 2 item 4: use the same predicate).TabbedForm.tsx,WizardForm.tsx,SplitForm.tsx,DrawerForm.tsx,ModalForm.tsx: one type line each, needed for the ruled type gain to compile (see above).index.tsx: theobject-master-detail-formfieldsregistration description said that any section memberfieldsdoes not list is dropped. This change makes that false for inline members, so the sentence is corrected in the same PR.packages/types/src/zod/objectql.zod.ts: a comment that names the declared entry type.scripts/check-spec-symbol-derivation.mjs: removesObjectFormSectionfromCLAIM_DEBT. Oncefieldsreferenced a spec type,check:spec-symbolsfailed on that stale entry and asks for the removal.--claim-ledgerprints the same block.packages/plugin-form/README.mdandcontent/docs/plugins/plugin-form.mdx: a short "What a section'sfieldsentries draw" section, required by AGENTS.md §5 Add automated testing infrastructure and CI/CD workflows #2.31a9d9a):packages/app-shell/src/views/metadata-admin/SchemaForm.groupSectionReachability-8725.test.tsx: the one downstream reader the type gain broke. It now names entries withsectionEntryName.index.tsx: publishessectionEntryName, and corrects theobject-formelement gate's docblock (comment only).packages/plugin-form/src/__tests__/inlineSectionEntry-11615.test.tsxandpackages/types/src/__tests__/object-form-section-field-entry-11615.test.ts.Verification
Patch round 1, on head
31a9d9a:turbo run type-check --filter='...@object-ui/types'covers@object-ui/typesand its 41 dependents that have a type-check script.a49b506first: 76 of 77 tasks passed, and the single error was the app-shell test's TS2339.31a9d9a: 77 of 77 tasks passed, exit 0.SchemaForm*.testfiles: Test Files 25 passed, Tests 301 passed.check:readme-exports,check:doc-snippetsandcheck:doc-exampleswere measured green this round.The readings below were taken on head
a49b506, the branch merged with origin/mainc096f03, and are unchanged by patch round 1. Round 1 re-ran theplugin-formandtypessuites green on31a9d9a.plugin-formtests (pnpm exec vitest run packages/plugin-form/): Test Files 164 passed (164), Tests 1906 passed and 1 skipped (1907).typestests (pnpm exec vitest run packages/types/): Test Files 357 passed (357), Tests 9500 passed (9500).scripts/__tests__that namecheck-spec-symbol-derivation: Test Files 12 passed (12), Tests 343 passed (343).pnpm --filter @object-ui/types run type-checkandpnpm --filter @object-ui/plugin-form run type-checkboth exit 0, afterturbo run build --filter=@object-ui/plugin-form^...built the dependencies.--listFilesOnlyconfirms that both new test files are in their package's test program.@object-ui/plugin-formand@object-ui/typesreport 0 errors.check-changeset-presence,check-changeset-no-major,check:changeset-claims,check:pending-changeset-literals,check:spec-symbols,check:doc-types,check:doc-fences,check:doc-example-ids,check:new-line-citations(0 new),check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:element-data-source-declaration,check:unreferenced-sources,check:prompt-keys,check:installed-pin-claimsandcheck:skills-paths.check-governed-queue-guard --testover the 19 paths reports NOT GOVERNED.check:sdui-registration-pins, which needs a console build.check:doc-snippets,check:doc-examplesandcheck:readme-exportswere measured green in patch round 1.Ablations. Each mutation went through
ablation-replace.mjs, and each was restored with the blob equal to HEAD andgit diff HEADempty:ObjectFormSection.fieldsset back to(string | FormField)[]. Thetypestest program fails with TS2322, only on row 1 of the new types pin (the{ field }entry).simplerows: the six-layout drawing row, both default-form intersection rows, the threesimplerows insubmitTargetRefusal, and the unit pin. The other five layouts stay green.hasInlineFields. 2 tests fail: thesimple"still work" row and the collector row.simpleBOUNDARY stays green, as expected.hasInlineFields. 1 test fails: thesimplecollector row, withexpected '' to be 'SEEDED'.Acceptance notes
objectNameand no data source, theobject-formelement gate requires a data source unlesscustomFieldsis non-empty. All six layouts now treat all-inline sections as an inline source. So on the page-block route, such a form gets the gate's notice before it reaches any layout. This errs on the loud side and is the same for every form type. Owner: none. Patch round 1 corrected the gate's docblock so it no longer claims to be in step with the layouts (comment only). Aligning the gate itself stays outside this card.formWritePayloadpasses the object definition unlesscustomFieldsis non-empty. This is unchanged and by design ("keys the object does not declare"). Owner: none.Session:
https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsALGenerated by Claude Code