Context
B2 field conditional rules (visibleWhen/readonlyWhen/requiredWhen) ship for standard form fields (PR #1578) and row-scoped in inline grids (PR #1579). The parent-scoped case — a line-item cell that reacts to the header record (e.g. lock all lines once parent.status == 'paid') — is deferred.
Why it's deferred (root cause)
The grid and the header ObjectForm both live inside MasterDetailForm. To re-gate grid cells when a header field (status) changes, the grid must re-render. But the watcher that scrapes the live header record into state re-renders MasterDetailForm → re-renders ObjectForm → fires its form.reset, wiping the user's input mid-edit/submit (the historical "Create, nothing happens" regression). Verified: broadening the watcher reproduced the wipe; backing it out restored green.
Plan
- Extract the line-items section of
packages/plugin-form/src/MasterDetailForm.tsx into a child component that owns the parentRecord state + the form-host watcher.
- A header change then re-renders only the grid subtree, never the reset-sensitive header form.
- Dedupe the scraped record (only setState on real value changes) to avoid mid-submit churn.
Already in place (plumbing)
@object-ui/core: evalFieldPredicate(pred, record, fallback, previous?, scope?) and resolveFieldRuleState(..., scope?) accept an extra scope (binds parent.*) — unit-tested.
@object-ui/fields: GridField accepts a contextRecord prop (bound as parent); deriveMasterDetail already carries readonlyWhen/requiredWhen onto columns.
So this is wiring + render-isolation, not a redesign.
Acceptance
- Showcase
invoice_line re-adds readonlyWhen: "parent.status == 'paid'" on quantity/unit_price/product (framework example).
- Live e2e: New Invoice → set Status = Paid → line cells lock; back to Draft → unlock.
- Full live e2e suite stays green (no
form.reset wipe regression on form-view-subforms / master-detail).
See docs/adr/0036-field-conditional-rules.md (the "Inline grids" + deferred-parent note).
Context
B2 field conditional rules (
visibleWhen/readonlyWhen/requiredWhen) ship for standard form fields (PR #1578) and row-scoped in inline grids (PR #1579). The parent-scoped case — a line-item cell that reacts to the header record (e.g. lock all lines onceparent.status == 'paid') — is deferred.Why it's deferred (root cause)
The grid and the header
ObjectFormboth live insideMasterDetailForm. To re-gate grid cells when a header field (status) changes, the grid must re-render. But the watcher that scrapes the live header record into state re-rendersMasterDetailForm→ re-rendersObjectForm→ fires itsform.reset, wiping the user's input mid-edit/submit (the historical "Create, nothing happens" regression). Verified: broadening the watcher reproduced the wipe; backing it out restored green.Plan
packages/plugin-form/src/MasterDetailForm.tsxinto a child component that owns theparentRecordstate + the form-host watcher.Already in place (plumbing)
@object-ui/core:evalFieldPredicate(pred, record, fallback, previous?, scope?)andresolveFieldRuleState(..., scope?)accept an extrascope(bindsparent.*) — unit-tested.@object-ui/fields:GridFieldaccepts acontextRecordprop (bound asparent);deriveMasterDetailalready carriesreadonlyWhen/requiredWhenonto columns.So this is wiring + render-isolation, not a redesign.
Acceptance
invoice_linere-addsreadonlyWhen: "parent.status == 'paid'"on quantity/unit_price/product (framework example).form.resetwipe regression onform-view-subforms/master-detail).See
docs/adr/0036-field-conditional-rules.md(the "Inline grids" + deferred-parent note).