You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SchemaRenderer's node visibility gate resolves a data.* predicate against the data-source ADAPTER, so a hoisted properties.visible written that way hides the block on every row, silently #5687
Found while implementing #4807 (row binding for record:alert's own properties.visible). It is one tier up from that card and was deliberately left alone there. Duplicate search run before filing: keyword + symbol sweep over open issues (SchemaRenderer / node-level visible / data.* / adapter / #5330 follow-ups) found nothing.
packages/react/src/SchemaRenderer.tsx hoists every properties.* value onto the node, so properties.visible arrives at the node-visibility chain as schema.visible and is evaluated there.
That chain's evaluator is built with data: dataSource — the data-source ADAPTER from SchemaRendererContext, defaulting to the module constant NO_DATA_SOURCE = {} when no provider is mounted. The docblock states this binding deliberately: data is what ${data.total} in a props bag resolves against, and the row must not overwrite it.
Consequence: an author who writes properties: { visible: "data.status == 'draft'" } on any block gets data.status resolved against the adapter, not the row. It does not throw, so it reads as a real verdict: undefined == 'draft' is a constant false, and the block is hidden on every row.
#5330 ruled B (maintainer, 2026-08-20): canon is record.*, and the row-action shorthand plus legacy data.* are deprecated but kept behind a survey-sized window. usePredicateRecordContext implements exactly that at the useCondition tier. Whether the ruling's reach extends to the node tier is a genuine contract question rather than a mechanical fix, because at that tier data already means something else and something documented:
binding the row over data there would change ${data.*} interpolation in props bags — the docblock calls that out as "a silent interpolation change nobody asked for";
packages/plugin-detail/src/renderers/__tests__/record-alert.rowBinding.test.tsx (added by #4807) is the standing evidence: its data.* leg has to hold the node gate open with a declared visibleWhen: cel('true') before the row-tier binding is observable at all, and the file's header states why. Dropping that isolation is the reproduction — the block goes hidden on both rows.
Scope note
record:alert was fixed at its own tier by #4807 and is not affected by whatever is decided here for the canonical record.* spelling; only the deprecated data.* spelling is, and only through the node gate. This issue is filed unassigned for triage.
Where this came from
Found while implementing #4807 (row binding for
record:alert's ownproperties.visible). It is one tier up from that card and was deliberately left alone there. Duplicate search run before filing: keyword + symbol sweep over open issues (SchemaRenderer/ node-levelvisible/data.*/ adapter / #5330 follow-ups) found nothing.Facts (read + measured on
origin/main3b147a3)packages/react/src/SchemaRenderer.tsxhoists everyproperties.*value onto the node, soproperties.visiblearrives at the node-visibility chain asschema.visibleand is evaluated there.data: dataSource— the data-source ADAPTER fromSchemaRendererContext, defaulting to the module constantNO_DATA_SOURCE = {}when no provider is mounted. The docblock states this binding deliberately:datais what${data.total}in a props bag resolves against, and the row must not overwrite it.properties: { visible: "data.status == 'draft'" }on any block getsdata.statusresolved against the adapter, not the row. It does not throw, so it reads as a real verdict:undefined == 'draft'is a constantfalse, and the block is hidden on every row.visibleWhenbinds norecordon record pages — everyrecord.*predicate fails soft to permanently visible, on every block #5454 keys off a throw; this path never throws, so nothing is printed. And the failure direction is hidden-rather-than-shown, which looks like the author's gate working.Why it is worth a card rather than a fix-in-place
#5330 ruled B (maintainer, 2026-08-20): canon is
record.*, and the row-action shorthand plus legacydata.*are deprecated but kept behind a survey-sized window.usePredicateRecordContextimplements exactly that at theuseConditiontier. Whether the ruling's reach extends to the node tier is a genuine contract question rather than a mechanical fix, because at that tierdataalready means something else and something documented:datathere would change${data.*}interpolation in props bags — the docblock calls that out as "a silent interpolation change nobody asked for";visible) answers differently depending on which tier consults it, which is what finding: record:alert 是唯一没接 #4075/#4077 行绑定的谓词面 —— 裸 { record } 作用域下 row-action 简写与 data.* 拼法 unbound,fail-soft 使作者门控的横幅永远显示 #4807's two-gate composition already makes hard to reason about;visibleWhenbinds norecordon record pages — everyrecord.*predicate fails soft to permanently visible, on every block #5454 made for the throwing paths — and let the deprecation window close on its own.Reproduction
packages/plugin-detail/src/renderers/__tests__/record-alert.rowBinding.test.tsx(added by #4807) is the standing evidence: itsdata.*leg has to hold the node gate open with a declaredvisibleWhen: cel('true')before the row-tier binding is observable at all, and the file's header states why. Dropping that isolation is the reproduction — the block goes hidden on both rows.Scope note
record:alertwas fixed at its own tier by #4807 and is not affected by whatever is decided here for the canonicalrecord.*spelling; only the deprecateddata.*spelling is, and only through the node gate. This issue is filed unassigned for triage.