…real host (#7190)
objectui#7190 censused the supply side — `InlineFieldInput` passes no
`dependentValues`, `ctx.formValues` does not exist — and graded itself a
`finding` rather than a bug on an explicit boundary: a detail page renders ONE
record, so a host that populates `ctx.data` would make the cascade resolve.
That was never measured, and a bare mount cannot measure it (no provider sets
`ctx.data`, so it gates trivially and always).
This mounts `RecordDetailView`, the real app-shell record page, loads a record
that carries the parent value, enters inline edit by double-click, and reads the
picker's own trigger. Both of `InlineFieldInput`'s call sites are covered —
`highlightFields` selects between them, because `buildDefaultTabs` hands the
strip's field list to the body as `hideFields`. Each test carries a live control
lookup: same reference, same record, no `dependsOn`, asserted enabled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
Part of #7190. ⛔ No closing keyword anywhere in this body: #7190 is a measurement card and whether the measurement retires it is the PM's call, not this PR's.
The probe ran in the REAL HOST —
RecordDetailView, the app-shell record page — not a bare mount. Every reading below depends on that sentence.Verdict
It gates. A
dependsOnlookup on a detail page renders a permanently gated, disabled picker —data-testid="lookup-trigger-gated", title "Select region first" — while theregionfield it names is on screen, in the same inline-edit session, two fields away, carryingemea.That is outcome ① of the card: a genuine second instance of #7165's defect. Recommended regrade of #7190:
finding⇒bug.Why a bare mount could not have answered this
#7190 graded itself a
findingon an explicit boundary: a detail page renders one record, which is exactly the "record scope" thatLookupField'sctx.datachannel exists to carry, so a host populatingctx.datawould make the cascade resolve and there would be no defect at all. A bare mount of the widget has no provider settingctx.data, so it reports a gated trigger trivially and always — an answer about the harness, not the product. So the new test mounts the whole record page and drives it as a user does: the record loads, a field is double-clicked to enter inline edit (#2401), and the picker's own trigger is read out of the resulting DOM.The measurement
packages/app-shell/src/views/RecordDetailView.lookupDependsOn-7190.test.tsx, 2 tests, both green against the current tree atdc7a48d02.dependsOn: ['region'])dependsOn)lookup-trigger-gatedlookup-trigger-ownerdisabledtruefalseThe control is asserted enabled, not observed: same reference, same record, same render, differing only in the declared key. A gated reading would mean nothing if the picker path were broken for both.
Both of the widget's call sites are covered, and both gate.
InlineFieldInputhas exactly two non-test call sites —DetailSection(details body) andHeaderHighlight(highlights strip). The canonical page routes a field to exactly one of them, becausebuildDefaultTabshands the strip's field list tobuildDefaultDetailsashideFields; declaringhighlightFieldstherefore selects the call site with no classname coupling and no double render, asserted as an exact trigger count of 2.Ablation — direction and counts predicted before running
Predicted: teaching only
DetailSectionto passdependentValuesturns the details-body test red and leaves the highlights-strip test green ⇒ 1 failed, 1 passed.Observed, exactly that:
Mutation proven on disk before the run — marker lines
InlineFieldInput=3,DetailSection=1, both blob hashes moved off their HEAD blobs. Restore proven by state, not by exit code:git diff HEAD,git diff --cachedandgit status --shortall empty, both blobs hash-identical to HEAD again, marker count back to 0 in both files. The mutation was visible without a rebuild, which is itself the proof that this suite resolves the package through source rather than a staledist.The ablation earns three things at once: the probe measures the
dependentValueschannel specifically and not an incidental fixture; the repair shape works; and the two tests really do drive different call sites, which is the surface claim the file makes without asserting on classnames.Is the repair the same shape as #7165's?
Same shape, different — and better — supply. The channel is identical: hand the widget the rendered record as
dependentValues. What differs is which record is available. #7188 records that the grid can only reach the saved row, becauserenderCellEditor's context is{ column, row, value, stage, commit, cancel }and carrying the pending record across that seam is the open half. The detail page has no such gap:InlineEditProvider/useInlineEdit()is already a live staged-value channel that both call sites hold —HeaderHighlightwrites throughinline.setField,DetailSectionreadsuseInlineEdit()?.fieldErrors. So a detail-page repair can feed the in-flight values #2215's form change chose, with the saved record beneath them, and would not need #7188's seam work.⭐ The residue is bigger than this card, and it changes the repair question
#7190's A2.3 anticipated that a
ctx.datarescue would be an implicit host contract nothing pins. The measurement says something stronger: no host can populate it.SchemaRendererContextTypedeclares exactly four members —dataSource,debug,debugFlags,apiFetch. There is nodataand noformValues.packages/react/src/context/SchemaRendererContext.tsx:46, whose value is{ dataSource, debug, debugFlags, apiFetch }. Control: 96 total matches for that provider including tests, so the "exactly one" is a reading, not a dead grep.⇒
dependentValues ?? ctx.formValues ?? ctx.data ?? {}has an unconditionally empty tail in production. Two widgets read that chain —LookupFieldanduseCascadingOptions(which drives the select / radio / checkboxes / multiselect cascades) — and at least three comments describe the tail as live, one of them naming it "the OUTER page's record". So this is not a detail-page gap: it is repo-wide, and #7165 is another instance of the same root.That reframes the repair as a genuine fork — per-call-site prop drilling, or making the context channel real, or retiring the dead tail. Filed separately as objectui#7206 rather than ridden here.
Verification
pnpm exec vitest run packages/app-shell/src/views/RecordDetailView.lookupDependsOn-7190.test.tsx—Test Files 1 passed (1),Tests 2 passed (2), atdc7a48d02.pnpm exec turbo run lint --concurrency=2— whole repo,Tasks: 47 successful, 47 total, 0 errors.pnpm --filter @object-ui/app-shell run type-check— green. Verified the new file is genuinely in that program:tsc -p tsconfig.test.json --listFilesnames it (1 hit), against a known existing test file as the control (1 hit).check:control-bytes,check:vi-mock-inherit,check:vi-mock-specifiers,check:entry-guard,check-changeset-presence— all green atdc7a48d02, each quoting its own verdict line with a non-zero population.Changeset
Empty frontmatter — measurement only, no behaviour change.
check-changeset-presencenames that the explicit exemption.Generated by Claude Code