Found while wiring @object-ui/plugin-grid's tests into tsc for #4040 (tranche 1). Filed rather than fixed: closing it changes a public exported interface, which is a contract decision, not execution. @object-ui/plugin-grid stays in TEST_DEBT until this is ruled on — its test project cannot go green without either this decision or an as any at the call site, and the second would encode exactly the wrong understanding this whole card exists to stop.
What the compiler says the moment it is allowed to look
packages/plugin-grid/src/__tests__/serverPagination.test.tsx, with the #3032 test-project template applied:
src/__tests__/serverPagination.test.tsx(182,11): error TS2322: Type '{ schema: any; dataSource: any; data: { id: string; name: string; }[]; manualPagination: true; rowCount: number; page: number; pageSize: number; onPageChange: Mock< Procedure >; onPageSizeChange: Mock< ... >; }' is not assignable to type 'IntrinsicAttributes & ObjectGridProps'.
Property 'data' does not exist on type 'IntrinsicAttributes & ObjectGridProps'.
The gap
ObjectGridProps (src/ObjectGrid.tsx:160) declares 12 members: schema, dataSource, className, onRowClick, onEdit, onDelete, onBulkDelete, onCellChange, onRowSave, onBatchSave, onRowSelect, onAddRecord.
The component collects everything else into ...rest (:269) and reads twelve further props out of it, each through an as any cast:
| prop |
read at |
data |
:384 — const passedData = (rest as any).data; |
manualPagination |
:420 |
rowCount |
:421, :2162 |
onPageChange |
:422, :2168 |
page |
:2163 |
pageSize |
:2165 |
onPageSizeChange |
:2171 |
sort |
:2188 |
onSortChange |
:2191 |
search |
:2201 |
onSearchChange |
:2204 |
onColumnStateChange |
:359 |
These are not accidental. :418 says so in as many words — "data arrives via rest (a prop), so do these too" — and the block implements the host-driven external pagination path from framework #2212. The same vocabulary IS declared one layer down, on DataTableSchema (packages/types/src/data-display.ts:400+), which ObjectGrid builds and forwards to (:2207).
Why nothing has caught it
The only non-test caller that could pass them is ObjectGridRenderer (src/index.tsx:70), typed React.FC< { schema: any; [key: string]: any } > and spreading {...props} straight through. An index signature of any accepts every one of the twelve, so the renderer path compiles and the gap is invisible from there. Every typed caller (plugin-view/src/ObjectView.tsx:1094, plugin-designer ×2, apps/console/src/dev/DevRowActions.tsx) passes only declared props, so none of them feels it either.
And the tests that exercise the path were not compiled by anything, which is #4040's premise exactly.
The decision
Not obviously one-sided, which is why it is filed instead of guessed:
- A — declare them on
ObjectGridProps. Matches what the component really accepts; makes the external-pagination contract checkable at every call site; is a public surface expansion on a widely-consumed block.
- B — declare them as a separate, documented interface (e.g.
ObjectGridExternalPaginationProps) that ObjectGridProps extends or that the host path is typed with. Keeps the "advanced host-driven" mode distinguishable from the ordinary authoring surface.
- C — rule that the escape hatch is deliberate and the props are internal, in which case the twelve
(rest as any) reads want a named internal type and ObjectGridRenderer's any index signature is the thing to tighten.
All three make the test compile without weakening it. Widening nothing and casting in the test is the one option that should not be taken: it would leave the same undeclared contract in place with a green tick over it.
Also seen in the same package, unblocked but not yet fixed
src/__tests__/useBulkExecutor.test.ts:406 — runAggregate is stubbed vi.fn(async () => undefined), no parameters, while BulkExecutorOptions.runAggregate (src/hooks/useBulkExecutor.ts:127) is (def, rows, params) => Promise< unknown > and the hook calls it with all three (:273). The case then asserts on runAggregate.mock.calls[0][1], the second argument — which the declared stub says does not exist:
src/__tests__/useBulkExecutor.test.ts(406,15): error TS2352: Conversion of type 'undefined' to type 'unknown[]' may be a mistake ...
src/__tests__/useBulkExecutor.test.ts(406,42): error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
It passes at runtime — vitest records real arguments whatever the declared signature — so this is a types-only lie, fixable by typing the stub's parameters. Recorded here so it travels with the package rather than being re-discovered.
Also worth knowing for whoever picks this up: TEST_DEBT declares 2 errors for @object-ui/plugin-grid, measured at #3032 branch time. Re-measured against 4cb0562b5 it is 4 code-tier (plus one config-tier TS2550, Array.prototype.at, resolved by the test project's lib). main moved; the fourth is importMissingRequiredHint.test.tsx:35 omitting ImportWizardProps.dataSource, which is a plain fixture fix.
Refs #4040, objectstack-ai/objectstack#4118.
Found while wiring
@object-ui/plugin-grid's tests intotscfor #4040 (tranche 1). Filed rather than fixed: closing it changes a public exported interface, which is a contract decision, not execution.@object-ui/plugin-gridstays inTEST_DEBTuntil this is ruled on — its test project cannot go green without either this decision or anas anyat the call site, and the second would encode exactly the wrong understanding this whole card exists to stop.What the compiler says the moment it is allowed to look
packages/plugin-grid/src/__tests__/serverPagination.test.tsx, with the #3032 test-project template applied:The gap
ObjectGridProps(src/ObjectGrid.tsx:160) declares 12 members:schema,dataSource,className,onRowClick,onEdit,onDelete,onBulkDelete,onCellChange,onRowSave,onBatchSave,onRowSelect,onAddRecord.The component collects everything else into
...rest(:269) and reads twelve further props out of it, each through anas anycast:data:384—const passedData = (rest as any).data;manualPagination:420rowCount:421,:2162onPageChange:422,:2168page:2163pageSize:2165onPageSizeChange:2171sort:2188onSortChange:2191search:2201onSearchChange:2204onColumnStateChange:359These are not accidental.
:418says so in as many words — "dataarrives viarest(a prop), so do these too" — and the block implements the host-driven external pagination path from framework #2212. The same vocabulary IS declared one layer down, onDataTableSchema(packages/types/src/data-display.ts:400+), whichObjectGridbuilds and forwards to (:2207).Why nothing has caught it
The only non-test caller that could pass them is
ObjectGridRenderer(src/index.tsx:70), typedReact.FC< { schema: any; [key: string]: any } >and spreading{...props}straight through. An index signature ofanyaccepts every one of the twelve, so the renderer path compiles and the gap is invisible from there. Every typed caller (plugin-view/src/ObjectView.tsx:1094,plugin-designer×2,apps/console/src/dev/DevRowActions.tsx) passes only declared props, so none of them feels it either.And the tests that exercise the path were not compiled by anything, which is #4040's premise exactly.
The decision
Not obviously one-sided, which is why it is filed instead of guessed:
ObjectGridProps. Matches what the component really accepts; makes the external-pagination contract checkable at every call site; is a public surface expansion on a widely-consumed block.ObjectGridExternalPaginationProps) thatObjectGridPropsextends or that the host path is typed with. Keeps the "advanced host-driven" mode distinguishable from the ordinary authoring surface.(rest as any)reads want a named internal type andObjectGridRenderer'sanyindex signature is the thing to tighten.All three make the test compile without weakening it. Widening nothing and casting in the test is the one option that should not be taken: it would leave the same undeclared contract in place with a green tick over it.
Also seen in the same package, unblocked but not yet fixed
src/__tests__/useBulkExecutor.test.ts:406—runAggregateis stubbedvi.fn(async () => undefined), no parameters, whileBulkExecutorOptions.runAggregate(src/hooks/useBulkExecutor.ts:127) is(def, rows, params) => Promise< unknown >and the hook calls it with all three (:273). The case then asserts onrunAggregate.mock.calls[0][1], the second argument — which the declared stub says does not exist:It passes at runtime — vitest records real arguments whatever the declared signature — so this is a types-only lie, fixable by typing the stub's parameters. Recorded here so it travels with the package rather than being re-discovered.
Also worth knowing for whoever picks this up:
TEST_DEBTdeclares 2 errors for@object-ui/plugin-grid, measured at #3032 branch time. Re-measured against4cb0562b5it is 4 code-tier (plus one config-tierTS2550,Array.prototype.at, resolved by the test project'slib).mainmoved; the fourth isimportMissingRequiredHint.test.tsx:35omittingImportWizardProps.dataSource, which is a plain fixture fix.Refs #4040, objectstack-ai/objectstack#4118.