Skip to content

[Decision] schema.onNavigate is a function value read off the grid schema — the fifth key #5091's ruling did not cover #5234

Description

@os-support-ai

> ### ⛔ CORRECTED 2026-08-18 — read this first
> An earlier version of this body claimed onNavigate is absent from the ObjectGridSchema interface and only type-checks because of BaseSchema's index signature. That was false. It is explicitly declared at packages/types/src/objectql.ts:847, with a doc comment. The error came from a grep bounded to lines 532–760 of an interface that runs 532–888. The body below is corrected, the affected argument is withdrawn, and the PM recommendation is withdrawn with it — see the correction comment for what moved and why.

Split out of #5091 by the repo:objectui PM seat (round 6, session session_01RV6yuVCxymHYE16PL9vQkE) at dispatch time, and deliberately excluded from that card's dispatch.

Why this is a separate card

#5091 catalogued four keys ObjectGrid reads but GRID_QUERY_INPUTS does not declare. A later comment added a fifthschema.onNavigate — on the reasoning that it belonged to the same decision, and it was attached to #5091 rather than filed separately so one decision would not be split across two cards.

The maintainer's 2026-08-18 ruling on #5091 (verbatim 「同意」) then enumerated only the original four: rowActionDefs → into the manifest; columnState / hideRowHeightToggle / maxInlineRowActions → non-author surface with exemption comments. onNavigate does not appear in it.

So the ruling and the card no longer cover the same key set. Rather than let a PM's reading close that gap, #5091 was dispatched for the four ruled keys with onNavigate explicitly out of scope, and the fifth comes here for its own answer.

Measured on origin/main

packages/plugin-grid/src/ObjectGrid.tsx:1187 onNavigate: schema.onNavigate,

read inside the useNavigationOverlay({ navigation, objectName, onNavigate, onRowClick }) call.

packages/types/src/objectql.ts:847 onNavigate?: (recordId: string | number, action?: string) => void;

declared on ObjectGridSchema (interface spans 532–888), with the doc comment "Callback for page-level navigation (used by 'page' mode). Called with recordId and action ('view' | 'edit')."

packages/plugin-grid/src/index.tsx no hit — absent from GRID_QUERY_INPUTS

It was invisible to #5091's original sweep because that sweep matched the cast form (schema as any).KEY, and this one is a plain read — the mirror image of the blind spot that produced the earlier misjudgement.

The actual shape of the problem

It is not an undeclared key absorbed by a loose index signature (the corrected reading). @object-ui/types declares it deliberately and documents it. The problem is narrower and now sits in exactly one place:

The renderer reads it and the TS type declares it, while the manifest surface — GRID_QUERY_INPUTS, the designer panel, the generated sdui-intrinsics.d.ts — denies it exists. That is the same "renderer reads it, manifest denies it" condition #4648 and #5091 exist to close.

In the same useNavigationOverlay call, schema.navigation and schema.objectName are both on the manifest surface; only onNavigate is not.

The argument that survives the correction, and is now the strongest one

A schema is a serialisable document, and a function cannot survive a metadata round-trip. Whatever declares it, onNavigate can never be written by an author in JSON or YAML, never round-trip through metadata, and never be produced by an AI writing a schema document. A (recordId, action) => void in a stored document is not expressible.

So the question is not whether it is declared — it is — but whether a callback that only a programmatic caller can supply belongs on the schema type at all, when nine sibling callbacks live on ObjectGridComponentProps for precisely that reason.

For contrast, verified key by key: onRowClick, onRowSelect, onCellChange, onRowSave, onBatchSave, onEdit, onDelete, onBulkDelete, onAddRecord exist only on ObjectGridComponentProps, with zero schema.on* read points. onNavigate is the one that is on both.

Four-prism

  • Platform coherence — nine callbacks are props-only; one is on the schema with a doc comment. Either that tenth is a deliberate exception that should be stated as one, or it is inconsistent. It already cost a documentation claim: the README and docs page said "the grid never reads a callback off the schema", narrowed to "never reads any of these nine" because of this key.
  • Measured business pull — none found for authoring it in metadata, and none is possible: JSON cannot carry a function. Programmatic callers are served by the props channel today.
  • AI-agent error-resistance — an agent reading ObjectGrid.tsx sees a callback taken off schema, checks @object-ui/types, finds it declared and documented, and reasonably concludes callbacks may be authored on a schema. It then emits metadata that silently does nothing. The corrected facts make this worse, not better: before, only the read site suggested it; now the type and its doc comment appear to confirm it.
  • Startup scope discipline — with the key explicitly declared, removing it is a breaking public type change plus a deprecation cycle, not the cheap tidy-up the earlier version of this card assumed.

The options

  • A. Move it off the schema read point and remove the declaration — onNavigate joins its nine siblings on ObjectGridComponentProps. Ends with one clean rule and restores the docs sentence as a universal. Cost, corrected: a breaking public type change on @object-ui/types plus a deprecation cycle — the same profile that decided FormSchema.onChange is a declared public callback the form renderer never calls — authoring it is a silent no-op #4259against removal. Requires checking for callers passing it through a schema object.
  • B. Declare it in GRID_QUERY_INPUTS so all three surfaces agree. Cheapest to execute and makes the manifest match the type. But it publishes to the designer panel a key no author can express, which is the objection that started this card.
  • C. Keep it as an explicit, documented exception — leave the type declaration and the read, add an exemption comment at the read site and a note on the type saying it is programmatic-only and deliberately outside the manifest. Cheapest that changes nothing observable; leaves the docs sentence narrowed.

PM recommendation: withdrawn. The earlier recommendation of A rested on the false premise that nothing declared the key. With the declaration real and deliberate, A's cost profile changes enough that I do not think a PM should be picking between these — it is a public-surface question with a real deprecation cost on one side and a published-but-unauthorable key on the other.

This is on the human floor: it moves a published surface, and #5091's ruling is the neighbouring decision it must stay consistent with.

Refs #5091 (the four ruled keys; its rowActionDefs line is also back with the maintainer, see PR5241), #5240 (userActions, a sixth key in the same family), #4648 (the prior round of this rule), #5065 / #5090 (same sweep).

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions