Skip to content

[finding] The object-view relay's viewDef is an untyped record, so a MISSING key rung is invisible to tsc — the same defect has now been fixed twice (description, rowColor) with a third half still open #7559

Description

@os-project-manager

Found while implementing objectui#7218 (relay a per-view rowColor). Filed rather
than folded into that PR: it is the MECHANISM behind that card's defect class, not
that card's defect, and acting on it is a design decision rather than a rung.

The observation

packages/app-shell/src/views/ObjectView.tsx builds the list schema its
renderListView hands to ListView by spreading the host's schema and then
relaying 47 named keys off the active view:

const fullSchema: ListViewSchema = {
    ...listSchema,
    label: viewDef.label ?? listSchema.label,
    description: viewDef.description ?? listSchema.description,
    ...45 more
};

The OUTPUT is typed — fullSchema: ListViewSchema. The INPUT is not. viewDef
is activeView, an element of the array buildViewTabs returns, whose declared
type is Array<Record<string, any> & { id: string }>. So every rung reads any.

⇒ The compiler checks that each key this literal WRITES is a legal
ListViewSchema member. Nothing checks the other direction — that a declared
ListViewSchema member the renderer READS actually has a rung. A key with no
rung is not a type error, not a lint finding, and not a test failure. It is
silence.

Why this is worth a card: the class has now recurred three times

card key state
objectui#7199 description fixed
objectui#7218 rowColor fixed (this finding's PR)
objectui#7516 fieldOrder open, needs-user-decision

All three are the same shape, and the card that found the second pair described
it exactly: "nothing errors, every authoring gate passes, and the only symptom
is that an authored column order (or row colour) has no effect on screen. An
author has no way to notice short of diffing the DOM."

Three instances of one mechanism, found one at a time by eye, is the signal that
the mechanism deserves the attention rather than the instances.

What this is NOT

⛔ Not the objectui#5043 family track. That one owns the (schema as any) reads
in plugin-view's ObjectView — typing the HOST-COMPOSITION block as an
explicit host-side prop contract. This is the other host and the other direction:
app-shell's relay has no casts to retire, it has a source object whose type is
Record<string, any> and a set of rungs nobody can enumerate against the target.

⛔ Not a claim that a rung is missing today. I did not census the remaining
ListViewSchema members against the 47 rungs — that census is most of the work
and is what a dispatched card would do first.

Not verified here

Whether the right answer is (a) type viewDef as the view-entry type it actually
carries so the compiler can see the gap, (b) a gate that enumerates
ListViewSchema members against the relay's key set and requires each absence to
be declared, or (c) neither, because a relay is deliberately a whitelist and the
absences are choices rather than omissions. Option (c) is a real possibility and
is why this is a finding rather than a bug: quickFilters: undefined in that
same literal is an absence that IS deliberate and documented.

There is prior art for the typing half being genuinely hard — objectui#4337
recorded that the legacy list-view vocabulary has no declared type at all, so
every fixture exercising the fold must cast.

Where to start

  • The relay: packages/app-shell/src/views/ObjectView.tsx, the fullSchema
    literal inside the renderListView callback.
  • The source type: buildViewTabs in the same file, and viewEntry in
    packages/app-shell/src/utils/viewIdentity.ts.
  • The target: ListViewSchema in packages/types/src/objectql.ts, which imports
    its spec-owned members by reference.
  • The sibling host, for contrast: packages/plugin-view/src/ObjectView.tsx, whose
    equivalent literal IS fenced and DOES have a pin that re-derives its read set
    from the source at test time (objectViewHostSurface.test.tsx) — a working
    example of the "enumerate the set mechanically" option applied to the other
    direction.

Filed unassigned, per the dispatch. Related: objectui#7199, objectui#7218,
objectui#7516, objectui#5043, objectui#4337 — none of them is this, and none of
them is duplicated by it.

Generated by Claude Code, session session_01EMrWaQw3XS5DxTHxp4yRyC, while
implementing objectui#7218.

Activity

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestfindingpm:dispatchedpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions