Repository navigation
bug(plugin-grid): a dependsOn lookup column is permanently uneditable in ObjectGrid — the inline editor supplies no dependent values, so the picker gates forever #7165
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 1, 2026 PM triage —
pm:blockedon PR #7167 landingFile collision:
packages/plugin-grid/is being edited by PR objectui#7167 (objectui#7154) right now. Unblocks automatically.Graded
bug, notfinding— this is a live user-facing defectA
dependsOnlookup column is permanently uneditable. The key arrives atLookupField(via the schema spread, not the copy set), and the picker gates forever because the grid supplies none ofLookupField's three dependent-value channels. PR objectui#2216 gave the form that injection; the grid never got it.That is not a cosmetic gap — an author who declares
dependsOnon a grid-edited lookup gets a control that can never be opened, with no diagnostic.How this was found is the point
I fenced objectui#7154 with "⛔
dependsOn— read objectui#2215 FIRST", expecting the answer to be a deferral. Reading it turned the deferral into a finding: objectui#2215 is closed COMPLETED, so "that was already handled" is true of the form and false of the grid. A fence meant to slow a risky key down instead located a defect nobody had attributed.⚠️ The seam decision is genuinely open and must not be pre-madeSaved-row vs staged-row — whether the dependent value comes from the persisted record or the in-flight edit — is a real design question with different behaviour for a user mid-edit. The card spells it out rather than choosing, which is correct.
⛔ Whoever takes this must not pick the cheaper channel and present it as the conclusion. If measurement does not settle it, it comes back to the maintainer. (objectui#6889 is this session's reminder that the cheap-looking branch is not reliably the affordable one.)
Generated by Claude Code
Unblocked — PR #7167 landed (
47035ce79); dispatch-readiness note so the seam does not stall this cardpm:blocked→pm:queuewas applied when #7167 merged (verified in the tree, not by sha: the newlookupPickerKeys-7154.test.tsxis present alongside the controllookupDisplayFieldSpelling-6875.test.tsx). The file collision is gone.⛔ Not dispatched this round — the seat's 3 running dev lanes are full (#6252, #6771, #7166). This card is first in line when one frees: it is the only
bug-graded live user-facing defect in the queue.Sequencing the A/B seam — decidable by measurement, so it need not go back to the maintainer first
My 08:19 triage fenced this: "whoever takes this must not pick the cheaper channel and present it as the conclusion." That fence stands, and it does not mean the card must wait. What it means is that A can never be the answer. Here is the order that respects both:
Measurement 1, before any code — is B Clause ②-engaged? B needs
renderCellEditor's editor context ({ column, row, value, stage, commit, cancel }) to carry the pending record. The question is whether that context type is published surface (reachable from@object-ui/types/DataTableSchema's declared editor context) or internal toplugin-grid.- B is NOT Clause ② ⇒ ⭐ do B. It is the card's own recommendation, it matches the form's semantics from PR fix(fields): unlock dependent lookups on parent selection and enforce the cascade on every picker #2216, and there is then no reason to ship an interim.
- B IS Clause ② ⇒ B is unlandable today whatever its merit — the fable
CONTRACT_REVIEW_TIERquota is exhausted (it is what holds finding(types/plugin-charts): the staticChartSchemanode's two data keys are declared-but-unread —ChartDataSeries.datais required and never read, andcategoriesis read as a SERIES list, not axis labels #6896/PR feat(types)!: retire ChartDataSeries.data, correct categories' prose (#6896) #7111, finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it #6881/PR feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) #7066, finding(types): 18 schemas narrow inheriteddisabledtobooleanwhile 0 narrow its twinvisible— no ruling says which is intended #7087, and steps 2–5 of finding(sdui-parser/components): the manifest tier reportsunknown-proponbody— the one child-list key 10+ registrations actually honour #6771). ⇒ ship A as an explicitly-labelled interim:dependentValues: ctx.row, with the staleness written into the code as a comment naming this card and the follow-up, plus a filed card for B. ⛔ A does not close this card in that branch — it downgrades a permanently-uneditable field to a field scoped by the saved value, and the card stays open for B.
The asymmetry that makes A acceptable as an interim only: a field that can never be filled is a hard defect with no workaround; a field scoped by the persisted parent rather than the staged one is wrong only in the narrower case where the parent was edited in the same unsaved session. Strictly better, and strictly not finished.
⚠️ Whichever branch runs, the staleness in A is the same silent-wrongness class this seat landed three refusals against this week (#7146no-positive-flow, #7147no-positive-magnitude, #7148unsized-rows). If A ships, the question "should the user be told the scope is from the saved row" is live, not settled — do not let it disappear into a comment.Two unmeasured items to carry into the dispatch
- Incidence is unmeasured: whether any shipped app declares
dependsOnon a field that also appears in an editable grid. Reachable by construction from the spec, but no user report is attached and this was a test-environment rendering measurement, not a browser dogfood run. ⛔ Do not let that gap be reported as "no impact." plugin-detail'sInlineFieldInputrenders the same widgets and was NOT measured — it may carry the identical gap. Worth one probe during the dispatch; if it does, that is a second card, not a widening of this one.
Generated by Claude Code
Claim —
domain:uiexecution seat, R19- Session:
session_012wwHa4aaFybxXrfmfHioDM - Branch:
claude/issue-7165-grid-dependent-values - Domain:
ui· Grade:bug(kept) · Worktree: dedicated, offorigin/main(899730e0aor later) - Serial constraints cleared:
plugin-gridis momentarily contended — finding(plugin-grid): 6 of the 10 keys ObjectGrid copies onto a column'sfieldMetahave no reader on that bag — the picker keys reach the editor by a different seam #7166 is running in the same package.⚠️ Declared rather than ignored: finding(plugin-grid): 6 of the 10 keys ObjectGrid copies onto a column'sfieldMetahave no reader on that bag — the picker keys reach the editor by a different seam #7166 touchesrelationalMetaKeys.tsand theapplyRelationalMetacopy set; this card touchesObjectGrid.renderCellEditor's context construction. Disjoint symbols in the same package. The dispatch order tells this lane to re-derive on whatever head exists at branch time and to expect a merge frommainbefore pushing.
The sequencing is already ruled in comment 5491922231 and is carried into the dispatch order verbatim: measurement 1 decides A vs B, and ⛔ A is never the conclusion.
Dispatching now because a lane freed (#6252 → PR #7180, #6771 → PR #7184, both reported). This is the only
bug-graded live user-facing defect in the queue — adependsOnlookup column that can never be opened, with no diagnostic.
Generated by Claude Code
- Session:
os-dev-report
{ "issue": 7165, "status": "done", "branch": "claude/issue-7165-grid-dependent-values", "pr": "https://github.com/objectstack-ai/objectui/pull/7193", "premise_still_valid": true, "summary": "MEASUREMENT 1: B IS Clause 2-engaged — so option A shipped as the labelled interim and ⛔ #7165 MUST STAY OPEN (the PR says 'Part of #7165', never a closing keyword). The editor context is published surface on both disjuncts of the test: `DataTableSchema.renderCellEditor` is declared in packages/types/src/data-display.ts with a Zod mirror, declared by maintainer ruling 2026-08-30 (#6882) replacing a `(schema as any)` cast, and its exact shape is pinned by an `Equal` (not `extends`) type assertion in packages/types/src/__tests__/data-table-declared-keys-6882.test.ts whose comment states the shape is pinned deliberately; ObjectGrid.tsx independently fences that package as 'published surface with its own review floor'. B needs a seventh ctx member, so it edits @object-ui/types, its Zod mirror and that maintainer-ruled pin. All three ruling conditions are met: the interim is labelled in code naming #7165 and the follow-up and stating exactly what is stale, the card is not closed, and B is filed as #7188. Premise re-measured on head 899730e0a: the existing pin still passed before my change, i.e. the defect was still live. A2.1/A2.3/A2.4/A2.5 all confirmed; A2.2 needed one CORRECTION — the declared contract always carried `row`, but ObjectGrid's LOCAL ctx annotation omitted it, so A was a two-part edit (widen the local annotation, then pass it), not a literal one-liner.", "tests": "All runs from repo root via the shared verify lock (objectui forbids `pnpm --filter PKG test`; path filters only). BASELINE on 899730e0a before any edit: `pnpm exec vitest run packages/plugin-grid/src/__tests__/lookupPickerKeys-7154.test.tsx` -> 'Test Files 1 passed (1) / Tests 5 passed (5)' — the gated pin still passed, so the defect was live on head. AFTER FIX, same command -> 'Tests 1 failed | 4 passed (5)', 'AssertionError: expected lookup-trigger-regional_owner to be lookup-trigger-gated' — the pin encoded the defect, so it was UPDATED IN PLACE (not deleted), header prose rewritten to say so. NEW FILE packages/plugin-grid/src/__tests__/gridDependentValues-7165.test.tsx, 4 tests each with a live control in the same render: (1) dependsOn column now opens, control column also opens; (2) picker SCOPED — $filter.region='north' reaches the query, Person 07 (south) absent, while the no-dependsOn control over the same records DOES offer Person 07; (3) NEGATIVE CONTROL — a row whose saved parent is empty still gates, proving the gate was not disabled; (4) the interim's staleness pinned as current behaviour, with its own proof the staging landed (region cell renders pending 'south' while the saved record reads 'north'), which is what stops it passing vacuously. Both files together: 'Test Files 2 passed (2) / Tests 9 passed (9)'. ABLATION — direction predicted BEFORE running (remove `dependentValues={ctx.row}` -> the 4 new tests plus the updated 7154 case go red, the other 4 stay green). NO REBUILD NEEDED, and that is measured not assumed: root vitest.config.mts aliases every @object-ui/* specifier to that package's src and the tests import ../ObjectGrid relatively, so nothing resolves through dist and a source mutation is directly live. Mutation confirmed ON DISK by marker count (1 -> 0) AND blob hash (e8ab332e… -> f219c865…), never by the editing tool's exit code. Result: 'Tests 5 failed | 4 passed (9)' — exactly the predicted set, passing count included. Restore proven BY STATE with HEAD named explicitly (`git checkout HEAD -- ABSPATH`, never the bare form which restores from the index): git diff HEAD, git diff --cached and git status --short all empty and the blob hash back to e8ab332e…; the script carried `trap ... EXIT INT TERM` with absolute paths. GATES: at final head aba0c7499 (after merging main, which had advanced to 8ec11e14f / #7180) — full packages/plugin-grid/ suite 'Test Files 105 passed (105) / Tests 949 passed (949)', check:control-bytes OK (5958 files), check:phantom-deps OK, check:changeset-no-major OK. At f17b08047 (identical tree for every path this PR touches): type-check plugin-grid + closure 14 tasks successful, lint of the affected package in FULL (not narrowed) 2 tasks 0 errors (751 pre-existing warnings), check:self-import OK, check:vi-mock-specifiers / check:vi-mock-inherit OK (4106 files, 525 with mocks), check-governed-queue-guard --test = NOT GOVERNED (4 paths vs 5 surfaces). Verdicts read from each gate's own printed line, with exit codes captured after redirection, never through a pipe. NOT-MEASURED check cleared: `tsc -p tsconfig.test.json --listFiles` lists all three edited files among its 1770, so the green typecheck genuinely covers the new tests rather than excluding them.", "mcp_calls": "11 — 2 issue reads, 2 targeted search_issues (each with a control word that hit: #7165 itself, then #7154/#2215), 2 issue creates, 1 PR create, 1 PR body read-back, 1 PR body update, 2 report comments. REST was 403 for repo-scoped reads and `gh` is absent, both measured with a control before switching channels.", "open_questions": [ { "question": "Should the grid TELL the user that a dependsOn picker is scoped by the SAVED parent while an unsaved edit to that parent is staged in the same row? The PM's ruling explicitly left this live rather than settled, and asked that it not disappear into a code comment.", "options": [ "A. Say nothing (what this PR ships). The picker silently uses the persisted parent. Zero new surface, but it is the same silent-wrongness class as the three refusals this seat landed in #7146 / #7147 / #7148.", "B. Surface a hint on the trigger/popover when the parent has a staged-but-unsaved value that differs from the saved one — e.g. 'scoped by the saved Region; save the row to re-scope'. Needs the staged record, i.e. it is gated behind exactly the same seam change as option B of this card.", "C. Do not build a message at all: land #7188, which removes the staleness and therefore removes the question." ], "recommendation": "C, and treat B as dead. Real business need: the staleness only exists because the interim exists, so a message would be UI built to describe a defect we have already scheduled to delete. Long-term soundness: contract-first — the honest fix is the declared seam member, not a renderer-side apology for a wrong input, and a warning would fossilize the interim as if it were a supported mode. Making AI-authored metadata hard to get wrong: an author declaring dependsOn should get correct cascading, not a caption explaining why it is stale — a warning trains authors to accept a wrong scope. Startup scope discipline: option B costs the SAME seam change as #7188 while delivering strictly less, which is the worst trade of the three. ⚠️ The one thing that would flip this to B: if #7188 is declined or deferred long, the silent staleness stops being temporary and then it must be surfaced. So this question should be closed by #7188 landing, not by a ruling that silence is acceptable." } ], "out_of_scope_findings": [ "filed as #7188: option B — carry the pending/staged record across the renderCellEditor seam so a dependsOn lookup is scoped by the in-flight parent (the seam change is spelled out: @object-ui/types ctx member + Zod mirror + the #6882 exact-shape pin + data-table.tsx passing `{ ...row, ...rowChanges }` + ObjectGrid reading it). Clause 2-engaged; #7165 stays open for it.", "filed as #7190 (label `finding`): plugin-detail's InlineFieldInput supplies no dependentValues either (measured: zero non-test occurrences; control: 88 files across packages/), and plugin-detail never provides SchemaRendererContext `data` itself. ⚠️ Deliberately NOT graded a bug — a detail page renders ONE record, so ctx.data may legitimately rescue the cascade there, and no rendering measurement was made. The single probe that settles it is written into the card.", "NOT filed, reported here as an observation for PM triage: ObjectGrid.tsx still lists `renderCellEditor` in its `ObjectGridDataTableSchemaHolds` type as an undeclared-but-live HELD key, with prose saying whether DataTableSchema should declare it is a pending packages/types ruling. That ruling HAPPENED — #6882 declared it on 2026-08-30 — so the hold entry and its surrounding census comment are stale. Harmless today (the intersection with the declared member is identical), but it is documentation that now states the opposite of the tree. Left untouched because it is outside this card and inside the file #7166 is adjacent to." ] }Generated by Claude Code
Generated by Claude Code
Interim landed in PR #7193 — ⛔ this card stays OPEN. Measurement 1: B IS Clause ②-engaged.
The A/B sequencing I ruled at dispatch resolved to the A branch, and every condition attached to it is met.
Why B is unavailable today — three independent Clause ② surfaces
surface evidence the declared type DataTableSchema.renderCellEditorinpackages/types/src/data-display.tsits Zod mirror packages/types/src/zod/data-display.zod.tsa maintainer-ruled exact-shape pin data-table-declared-keys-6882.test.ts, anEqual(notextends) assertion, whose comment says the shape is pinned deliberatelyDeclared by maintainer ruling #6882 (2026-08-30), replacing a
(schema as any)cast, andObjectGrid.tsxindependently fences that package as "published surface with its own review floor."⭐ The
Equal-not-extendsdetail is what makes this airtight: anextendspin would tolerate an added member and B might have squeaked through as internal. It does not. B needs a seventh ctx member and therefore edits all three.What shipped, and what it is not
A —
dependentValues: ctx.row, the saved record. AdependsOnlookup column is no longer permanently uneditable; the picker now scopes by the persisted parent ($filter reaches the query, and the control column over the same records still offers the row the filter excludes).⛔ A is not the answer to this card. The staleness is real and now pinned as current behaviour: a parent edited but not yet saved in the same row does not re-scope the child. B is filed as #7188 and this card stays open for it.
⚠️ One correction to my own dispatch: I called A "one line." The declared contract always carriedrow, butObjectGrid's local ctx annotation omitted it — so A was a two-part edit (widen the annotation, then pass it). A declared member that a local annotation silently narrows is a small hazard of its own: the type says the data is there, and the call site cannot see it.⚖️ Ruling on the "should we tell the user" question I left live: C
I asked at dispatch that this not disappear into a code comment. It does not, and the answer is C — close it by landing #7188, not by ruling that silence is acceptable.
The deciding argument is one I did not have when I asked: ⭐ a warning costs the same seam change as #7188 while delivering strictly less. A hint reading "scoped by the saved Region" needs the staged record — exactly the ctx member #7188 adds. Having paid that price, you would ship a caption explaining the wrong scope instead of shipping the right scope. And a message would fossilise the interim as a supported mode: an author who declares
dependsOnshould get correct cascading, not a caption training them to accept a wrong scope.⚠️ The flip condition, adopted verbatim and binding: if #7188 is declined, or deferred long enough that the interim stops being temporary, the silent staleness must be surfaced. The staleness is the same silent-wrongness class as the three refusals this seat landed this week (#7146no-positive-flow, #7147no-positive-magnitude, #7148unsized-rows), and it is tolerable only because it is scheduled for deletion. ⇒ This question is closed by #7188 landing, not by silence becoming the settled answer.Incidence — carried, not closed
The dispatch forbade reporting this gap as "no impact", and it was measured with controls in both corpora:
- this repo: 3 files match
dependsOn|depends_on, none is this defect (the gantt hit isdepends_onas task predecessor links, a different key). Control: 88 files acrosspackages/. - hotcrm: 6
dependsOndeclarations on lookup fields across 5 objects (crm_contract×2,crm_case,crm_quote×2,crm_opportunity), all scoping contacts tocrm_account— but 0 editable grids in its views. Control: the views dir exists and was read.
⇒ ⭐ Neither measured corpus reaches the defect today, but hotcrm is ONE metadata key away —
editable: trueon any of those list views. That is the sentence that should drive #7188's priority. Customer apps remain unmeasured, and that is stated rather than glossed.Sibling filed
#7190 —
plugin-detail'sInlineFieldInputsupplies nodependentValueseither. ⛔ Deliberately not graded a bug: a detail page renders one record, soctx.datamay legitimately rescue the cascade there, and no rendering measurement was made. The single probe that settles it is written into that card.Keeping this card open and
pm:dispatchedoff; it returns to the queue for #7188 once theCONTRACT_REVIEW_TIERquota reopens.
Generated by Claude Code
- this repo: 3 files match
✅ Interim MERGED — PR #7193 is on
main(9c7490268). ⛔ Card remains OPEN andpm:blocked.Confirming the landing; the full ruling is already recorded in comment 5492682950 and is not repeated.
Verified by content with live controls:
probe result SUBJECT — dependentValuesinObjectGrid.tsx2 (the widened local annotation + the pass) SUBJECT — the interim labelled in code, naming #7188 3 SUBJECT — new suite gridDependentValues-7165.test.tsx1 CONTROL — renderCellEditorstill live in the same file13 ⭐ CONTROL — the #7154 pin still exists 1 ⭐ That last row is the one worth having. The #7154 pin encoded the defect — it asserted the gated trigger — so the fix necessarily turned it red. It was updated in place, not deleted, and its continued existence on
mainis what proves that. A deleted pin is indistinguishable from a pin that never existed, and deleting it would have been the easy way to make the suite green while erasing the only record that the gate was ever measured.State, unchanged and deliberate
- A
dependsOnlookup column is no longer permanently uneditable — the picker scopes by the persisted parent. - ⛔ The card does not close. B is The grid's inline cell editor scopes a
dependsOnlookup by the SAVED row, not the staged one — carry the pending record across therenderCellEditorseam (option B of #7165) #7188, blocked on the fableCONTRACT_REVIEW_TIERquota; the interim's staleness (a parent edited but unsaved in the same row does not re-scope the child) is pinned as current behaviour, with its own proof the staging landed so the pin cannot pass vacuously. - The "should we tell the user" question is ruled C — closed by The grid's inline cell editor scopes a
dependsOnlookup by the SAVED row, not the staged one — carry the pending record across therenderCellEditorseam (option B of #7165) #7188 landing, ⛔ not by silence becoming the settled answer. The flip condition stands: if The grid's inline cell editor scopes adependsOnlookup by the SAVED row, not the staged one — carry the pending record across therenderCellEditorseam (option B of #7165) #7188 is declined or deferred long enough that the interim stops being temporary, the staleness must be surfaced.
Unblock-when: the
CONTRACT_REVIEW_TIERquota reopens, for #7188.
Generated by Claude Code
- A
Closed — completed: option B landed in PR #7241 (
adbda1bed), so the interim's staleness and the open disclosure question are both gonedomain:uiexecution seat, sessionsession_01NRRumy89BYdW9ogbcdHTho(os-litant). Provenance: this card's own ruling (comment 5492682950) — the "should the user be told the scope is from the saved row" question is answered C, closed by #7188 landing; the unblock condition recorded in comment 5493108627 (the contract-review tier reopens, for #7188) was met today, #7188 was built atfable, contract-reviewed in-seat (PASS, #7188 comment 5503049917) and merged.Verified on
origin/mainby content: the grid's editor now scopes byctx.pendingRow ?? ctx.row(1 hit) and the INTERIM block that named #7188 is deleted (0 hits for its closing phrase); the #7154 pin still exists (updated in place by PR #7193, never deleted) andgridDependentValues-7165.test.tsxtest 4 was flipped by PR #7241 into the assertion that goes red if anyone simplifies B back to A. The flip condition ("if #7188 is declined or deferred long, surface the staleness") is moot.Still open and unrelated to this close: #7190 (plugin-detail's
InlineFieldInput, its own card) and #7196 (the redundant local hold copy inObjectGrid.tsx, which PR #7241 keptEqualby carryingpendingRow).pm:blockedstripped and the stood-down seat's assignee cleared with the close.
Generated by Claude Code
Provenance: measured while implementing objectui#7154, which asked whether copying
dependsOnonto the grid's relational meta could expose or worsen objectui#2215. It cannot —dependsOnalready arrives — but the measurement surfaced this. Filed unassigned.What is measured
Rendered on
51449a043(origin/main), anObjectGridwitheditable: trueover an object schema carrying:and a row whose
regionis'north'. Single-click theregional_ownercell and the inline editor renders:data-testid=lookup-trigger-gated,disabled=true, text "Select region first"disabledtooThe control column in the same render (same reference, same records, no
dependsOn) giveslookup-trigger-owner, enabled. So the key arrives and is read — the gate is the proof — and the field can never be filled, because the value the gate reads never becomes non-empty.Pinned as current behaviour in
packages/plugin-grid/src/__tests__/lookupPickerKeys-7154.test.tsx(thedependsOncase), added by objectui#7154's PR.Root cause — the half of PR objectui#2216 the grid never got
objectui#2215 ("Cascading lookup (dependsOn) broken in forms; table picker bypasses the dependent filter") was closed COMPLETED by PR objectui#2216, which fixed two halves:
dependentValuesprop, anddependsOnchain as a hardbaseFilter, so no picker bypasses the cascade.Half 2 is host-independent and is live on the grid path. Half 1 is per-host, and the grid was never given it.
LookupFieldresolvesdependentValues ?? ctx.formValues ?? ctx.data ?? {}(packages/fields/src/widgets/LookupField.tsx, theresolvedDependentValuesmemo):dependentValues— the grid passes none.renderCellEditorinObjectGrid.tsxrendersFieldEditWidgetwithfield/value/onChangeonly.ctx.formValues—SchemaRendererContexthas no such member (that was objectui#2215's own finding, still true).ctx.data— the grid does not set it for a row.So the resolved record is
{}for every row,dependenciesMissingis permanentlytrue, and the gate never lifts. Grepped for the supply side:dependentValuesis passed bypackages/components/src/renderers/form/form.tsx,packages/app-shell/src/views/ActionParamDialog.tsxandpackages/plugin-grid/src/components/BulkActionDialog.tsx— the grid's own inline editor is not among them.Why this was not fixed in objectui#7154
It is a behaviour change outside that card (which is about the relational copy set), and its shape is a real decision rather than a mechanical fill-in.
renderCellEditor's context object already carriesrow, sodependentValues: ctx.rowis one line — butrowis the SAVED record. The form's answer to objectui#2215 was explicitly the LIVE watched record, so that a user picking a parent re-scopes the child immediately. In a grid the parent may be edited in the same row in the same session, and those staged values live in the table'spendingChanges, whichrenderCellEditoris not handed.Two readings, and they are not the same product:
ctx.row). One line, unblocks the field, scopes the query by what is persisted. A parent edited but not yet saved in the same row does not re-scope the child — silently stale rather than gated.DataTableSchemaeditor context to carry the pending record — a contract change on the seam (renderCellEditor's context is{ column, row, value, stage, commit, cancel }).Recommend B if the seam change is acceptable, A only as an interim with the staleness written down; the choice belongs to whoever owns that contract.
What is NOT measured
dependsOnon a field that also appears in an editable grid. The defect is reachable by construction from the spec (FieldSchema.dependsOnis declared and the grid honourseditable), but its incidence is unmeasured.plugin-detail'sInlineFieldInputrenders the same widgets and was not measured here; it may have the same gap.Generated by Claude Code