Skip to content

finding(react): the renderer seam's dataSource carries a second, undeclared meaning — the data scope bind and data.* resolve against — and typing the key prices it out for a conformant host #9308

Description

@os-tesla

Measured while implementing objectui#7912 (the ruled narrowing of the renderer seam's
dataSource). Filed unassigned and deliberately not repaired there: objectui#7912's ruling is about the ADAPTER
declaration and is binding, while the question below is a second published-surface question
with its own precedence problem. Recording it because #7912's narrowing is what makes it
visible, and because it is the one thing that narrowing costs.

⚠️ Angle-bracket shaped fragments are spelled out in words below: a tag-shaped fragment does
not survive this field, and the passages this card is about are JSX attributes.

Measured on origin/main at 243fc8317, and on the #7912 branch at c3e363b5c.

The site — one key, two meanings

SchemaRendererContext.dataSource is read for two unrelated purposes:

  1. The adapter. Plugins call find / getObjectSchema / aggregate on it. This is the
    meaning objectui#7912 declared: it is now the published DataSource contract.
  2. The data scope. The same value is the root that bind paths and data.* expressions
    resolve against:
    • packages/react/src/context/SchemaRendererContext.tsxuseDataScope(path) splits the
      path and walks it over context.dataSource.
    • packages/react/src/SchemaRenderer.tsx — the expression evaluator is constructed with
      data: dataSource, so every visibleWhen / hidden / disabled predicate resolves
      data.* against that same value.
    • packages/components/src/renderers/complex/dataTableBindDiagnostic.ts states the design
      in as many words: "bind is the data-scope binding vocabulary: a path string resolved by
      useDataScope(). list, tree-view and the object-* plugin widgets read it."

Production readers of meaning 2 today: components' list and tree-view,
plugin-charts' ObjectChart, plugin-dashboard' ObjectDataTable, plugin-list's
ObjectGallery — each useDataScope(schema.bind).

Why this is now a contract question and not just an oddity

A DataSource adapter has no users, no customers, no status member. So for a host that
injects a conformant adapter — which the seam now REQUIRES — every bind path and every
data.* predicate resolves to undefined. For a host that injects a data bag, they resolve,
and the adapter meaning is the one that breaks.

The two meanings were never compatible; any merely hid the choice. What changed with
objectui#7912 is that meaning 1 is declared, so meaning 2 is no longer expressible by a
conformant host: injecting a bag is now a type error, and the only way to keep it is a cast at
the injection site.

The repository already instruments the collision, which is the strongest evidence that it is
real and known: packages/react/src/utils/visibilityDiagnostic.ts carries
reportAdapterOnlyDataPredicate, a dev warning that fires when a data.* path does NOT
resolve against the injected value — an "adapter-only data predicate". That warning exists
because both meanings arrive through one key.

Measured population

On the objectui#7912 branch, every internal injection that relies on meaning 2 (or on a
partial double) is now marked with an explicit cast, so the population is greppable rather
than inferred:

  • 65 marked injection sites across 33 test files in 8 packages
    (grep -rn 'as unknown as DataSource').
  • 1 production site was a truthy empty-object stand-in and was repaired there; the
    remaining production readers of meaning 2 are the five useDataScope(schema.bind) callers
    listed above, which read the value rather than inject it, so they compile either way.

Options, none taken here

  • A. Give the data scope its own key on the provider (say a data prop), keep
    dataSource the adapter, and define the precedence between them. That is the option the
    dataTableBindDiagnostic header already flags as needing its own ruling, including a
    data-vs-bind precedence.
  • B. Retire meaning 2 at this seam: bind resolves only against a record scope
    (RecordContext) and never against the injected adapter. Decides objectui#7206's sibling
    question in the same direction.
  • C. Declare the union on the key (DataSource or a record bag). This is the half-typed
    seam objectui#7912's ruling refused, so it is listed only to be refused explicitly.

⛔ No recommendation is implied by the order. What this card asks for is a decision, because
today the answer is "both, through one key, and the compiler can only describe one of them".

Related: objectui#7912 (the adapter declaration, landed), objectui#7206 (the
formValues / data tail on the same context, deliberately undecided), objectui#6575 and
objectui#6665 (the bind diagnostics that map meaning 2's blast radius), objectui#7483 (the
same defect class on ObjectView's props).

Filed by an ObjectUI development agent seat (Claude Code) while implementing objectui#7912;
the measurement commands and their readings are quoted above rather than attached.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpackage: reactpm:dispatchedpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions