Found while implementing #6300 (PR #6386), which fixed the equivalent hole in the five variant renderers. Out of scope there — this lands in ObjectForm.tsx, outside that card's five files, and it is #6176's defect class ("a declared submitHandler is not consulted") rather than #6300's ("a form with no persistence target reports success"). Filed unassigned and ungraded; routing and grading are triage's.
Dedup done before filing, with a control: a keyword pass (SimpleObjectForm, submitHandler, hasInlineFields, customFields, inline fields) over all 250 open issues in this repo, read from the REST list endpoint (/issues?state=open&per_page=100, pages 1-5 — GitHub search is 403 from this container seat, list is 200). Three hits, none of them this: #6300 (the card being implemented), #6152 (unrelated schema-mirror ledger), #6366 (unrelated spec-bridge field normalizer). So the zero is a real reading.
The defect
SimpleObjectForm.handleSubmit (packages/plugin-form/src/ObjectForm.tsx) opens with
// For inline fields without a dataSource, just call the success callbackif(hasInlineFields&&!dataSource){if(schema.onSuccess){awaitschema.onSuccess(formData);}returnformData;}The carve-out is checked before the persistence chain, so a declared submitHandler is never reached. ObjectFormSchema.submitHandler is documented as "the form validates and hands the collected values to the host INSTEAD of calling dataSource.create / dataSource.update" — a seam that by construction needs no adapter of its own. A host that declared it owns the write gets a success signal for a write it was never asked to perform.
Measured
Driving formType: 'simple' through ObjectForm with customFields (inline), a submitHandler, and nodataSource:
| fixture | onSuccess | submitHandler |
|---|
simple + customFields + submitHandler, no dataSource | 1 | 0 |
simple + customFields, no submitHandler, no dataSource (control — the legitimate inline collector) | 1 | n/a |
Measured on 2e11c8c5b. The control is what shows the first row is about the seam being skipped, not about the carve-out firing at all.
Why it is worth a decision rather than a reflex fix
PR #6386 rules the opposite way for the five variant renderers, following #6300's triage direction: "when a submitHandler is declared, a missing dataSource must never bypass it." So as of that PR, simple and the other five disagree in exactly this cell:
| simple | tabbed / wizard / split / drawer / modal |
|---|
inline fields + submitHandler + no dataSource | onSuccess(formData), seam skipped | seam runs, then onSuccess(result) |
One renderer family answering a declared seam two ways is the "N dialects" shape commandment #0.1 exists to prevent. The straightforward resolution is to move the carve-out after the submitHandler check in SimpleObjectForm, matching the five — mechanically small, and it is what the #6300 triage ruling already says for every renderer ObjectForm routes to. Recording it as an issue rather than doing it inside #6300's PR because it is a different card's surface and a different card's defect class.
Suggested surface
packages/plugin-form/src/ObjectForm.tsx, the hasInlineFields && !dataSource early return in SimpleObjectForm.handleSubmit. packages/plugin-form/src/submitTargetRefusal.test.tsx (added by PR #6386) is the natural home for the pin — its block 1 already asserts exactly this property for the other five.
Generated by Claude Code
Found while implementing #6300 (PR #6386), which fixed the equivalent hole in the five variant renderers. Out of scope there — this lands in
ObjectForm.tsx, outside that card's five files, and it is #6176's defect class ("a declaredsubmitHandleris not consulted") rather than #6300's ("a form with no persistence target reports success"). Filed unassigned and ungraded; routing and grading are triage's.Dedup done before filing, with a control: a keyword pass (
SimpleObjectForm,submitHandler,hasInlineFields,customFields,inline fields) over all 250 open issues in this repo, read from the REST list endpoint (/issues?state=open&per_page=100, pages 1-5 — GitHub search is 403 from this container seat, list is 200). Three hits, none of them this: #6300 (the card being implemented), #6152 (unrelated schema-mirror ledger), #6366 (unrelated spec-bridge field normalizer). So the zero is a real reading.The defect
SimpleObjectForm.handleSubmit(packages/plugin-form/src/ObjectForm.tsx) opens withThe carve-out is checked before the persistence chain, so a declared
submitHandleris never reached.ObjectFormSchema.submitHandleris documented as "the form validates and hands the collected values to the host INSTEAD of calling dataSource.create / dataSource.update" — a seam that by construction needs no adapter of its own. A host that declared it owns the write gets a success signal for a write it was never asked to perform.Measured
Driving
formType: 'simple'throughObjectFormwithcustomFields(inline), asubmitHandler, and nodataSource:onSuccesssubmitHandlersimple+customFields+submitHandler, nodataSourcesimple+customFields, nosubmitHandler, nodataSource(control — the legitimate inline collector)Measured on
2e11c8c5b. The control is what shows the first row is about the seam being skipped, not about the carve-out firing at all.Why it is worth a decision rather than a reflex fix
PR #6386 rules the opposite way for the five variant renderers, following #6300's triage direction: "when a
submitHandleris declared, a missingdataSourcemust never bypass it." So as of that PR,simpleand the other five disagree in exactly this cell:simpletabbed/wizard/split/drawer/modalsubmitHandler+ nodataSourceonSuccess(formData), seam skippedonSuccess(result)One renderer family answering a declared seam two ways is the "N dialects" shape commandment #0.1 exists to prevent. The straightforward resolution is to move the carve-out after the
submitHandlercheck inSimpleObjectForm, matching the five — mechanically small, and it is what the #6300 triage ruling already says for every rendererObjectFormroutes to. Recording it as an issue rather than doing it inside #6300's PR because it is a different card's surface and a different card's defect class.Suggested surface
packages/plugin-form/src/ObjectForm.tsx, thehasInlineFields && !dataSourceearly return inSimpleObjectForm.handleSubmit.packages/plugin-form/src/submitTargetRefusal.test.tsx(added by PR #6386) is the natural home for the pin — its block 1 already asserts exactly this property for the other five.Generated by Claude Code