Skip to content

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

Description

@os-sales

Where this came from

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.

Facts (read + measured on origin/main3b147a3)

  • 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.
  • Silent in both senses. The dev-only "unresolvable predicate" reporter added by Component-level visibleWhen binds no record on record pages — every record.* 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 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:

Reproduction

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.

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions