You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
QueryResult (packages/types/src/data.ts) declares exactly one rows member — data — alongside total, page, pageSize, hasMore, cursor, metadata. records is not a member: it is the spelling the server envelope and the client SDK use, which ObjectStackAdapter.normalizeQueryResult maps to data before returning. #5945 removed it from two app-shell readers; this removes it from the seven more the same producer sweep turned up, one module at a time, each with its own pin.
⛔ QueryResult is not widened to bless records. That is a published-type change and a maintainer decision.
related-count-store.ts is the one that was actively wrong: it read records ahead of data, so a find() answer carrying both would have been counted from the key the contract does not declare. Its pin asserts that case directly ({ records: 3 rows, data: 1 row } counts 1, not 3). The other six read data first, so their records arm was dead — and a dead tolerant arm is still where a non-conforming producer keeps working unrejected (AGENTS.md #0.1).
One repeated shape, not seven different treatments. Every module got the same two steps #5945 used — measure, then delete and pin — and each pin has the same three legs where the module has them: the contract read, the live arms, the refusal. Nothing here argues for splitting the card.
The measurement, and its control
Repo-wide over every tracked file (git ls-files, 5,732 files — sources, tests, examples, skills/, scripts/, fixture data), for an object-literal records: key plus .records = assignment: 112 sites, zero of them a producer at the DataSource.find() seam.
Read directly, every find() implementation in the repo returns data or a bare array:
ObjectStackAdapter.normalizeQueryResult (data-objectstack/src/index.ts:3246) consumes the server/SDK records (and value) envelope and returns { data, total, page, pageSize, hasMore }.
ApiDataSource.normalizeQueryResult (core/src/adapters/ApiDataSource.ts:382) reads records off the raw HTTP payload and likewise emits data.
ValueDataSource, runner's MockDataSource, apps/site's gallery source, examples/byo-backend-console's mock, the gantt demo source and the @object-ui/types REST example all return data (or a bare array).
related-count-store.ts's own ProbeFn never declared records in the first place — its return type is { total?, data? } | unknown[] | { length? }. Only the res: any cast let the arm through.
Control for the zero (a zero counts only if a term that must hit is run in the same query and does): the same sweep DOES find every legitimate records producer, on other seams — ViewDataProvider.ts (12), its test (8), the client-SDK doubles below normalizeQueryResult (apiAccessDenied.test.ts, consoleUserPreferenceBudget.test.tsx, v3-compat.test.ts), the raw HTTP payload doubles (objectListApiDisabled-4408.test.tsx, packagedActions.test.ts), plus #5945's own two asRecords envelopes. 45+ hits. A producer at the find() seam would have been caught by the identical query.
A second control lives inside each pin: the data leg and (where the module has one) the bare-array leg push the same rows through the same mount and both render. So a records leg that renders nothing rendered nothing because the envelope was refused, not because the harness was dead.
Ablation — each pin red with the arm restored, green without it
Per module: restore the pre-fix expression, prove the mutation reached disk (the exact pre-fix text 0 → 1 and the post-fix text 1 → 0 — never an editor exit code), run that module's pin, restore with git checkout HEAD --, then prove the restore (git diff HEAD empty andgit hash-object equal to the HEAD blob). Every module passed both proofs.
module
ablated
green (this PR)
related-count-store
2 failed / 3 passed (5)
5 passed
data-list
1 failed / 2 passed (3)
3 passed
elements (element:number)
1 failed / 2 passed (3)
3 passed
record-picker
1 failed / 2 passed (3)
3 passed
record-activity
1 failed / 1 passed (2)
2 passed
record-history
1 failed / 1 passed (2)
2 passed
ObjectView
1 failed / 2 passed (3)
3 passed
In every case the reds are exactly the records assertions and the live-arm legs stay green — the pins discriminate the deleted arm, they do not merely fail. related-count-store reds twice because it is the only module with a precedence case to assert as well.
No rebuild leg is owed here, and here is why rather than an assumption: the root vitest.config.mts aliases every @object-ui/* specifier to packages/*/src (lines 268-284), and each pin imports its module under test by relative path. Nothing in these runs resolves through a dist/, so a stale build cannot make an ablation falsely green. The console was built for the two bundle-artifact gates below, and those read dist/ as intended.
Exclusions — confirmed, not assumed, and untouched
app-shell/src/console/marketplace/marketplaceApi.ts:354,403 — raw fetch() of Cloud's /api/v1/data/...; its own comment states the API returns { object, records, total, hasMore }. A raw HTTP payload, not a QueryResult. Correct as it stands.
app-shell/src/views/setup/packagedActions.ts:108 — readDataRecords reads the transport envelope's FindDataResponse = { object, records, total?, hasMore? }, wrapped or bare. Its header already says these are the two forms of one response, ⛔ not a tolerant alias. Correct as it stands.
core/src/data-scope/ViewDataProvider.ts — DataFetcher.fetchRecords is declared Promise of { records, total } and ResolvedData declares records. Its own interface, its own seam. Correct as it stands.
What moved
Nothing. No existing test was edited, renamed or deleted; the seven pins are all new files. Full suites for the three touched packages: 354 files, 3,276 tests, 0 failures.
Verification — all on b2a4ca1c9, this branch's head
Run from the repo root, never pnpm --filter PKG test (objectui#3378 / #3288 — that re-roots vitest at the package and reports another package's files passing while running none of yours).
Pins, executed file names shown by --reporter=verbose: pnpm exec vitest run over the seven *.contractEnvelope-6726.* files — Test Files 7 passed (7) / Tests 21 passed (21).
Package suites: pnpm exec vitest run packages/plugin-view/ packages/plugin-detail/ — Test Files 141 passed (141) / Tests 1302 passed (1302); pnpm exec vitest run packages/components/ — Test Files 213 passed (213) / Tests 1974 passed (1974).
pnpm --filter @object-ui/components --filter @object-ui/plugin-detail --filter @object-ui/plugin-view run type-check — 3 of 47 projects, all Done. Each package's type-check is tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms all seven new test files are in those programs (4 / 2 / 1), so the green covers them rather than merely passing beside them.
pnpm lint (turbo run lint, eslint . --no-inline-config) — repo-wide, Tasks: 47 successful, 47 total, 0 errors. Not narrowed.
…ryResult declares them
Remove the `records` arm from the seven `DataSource.find()` consumers
objectui#5945 did not name, and pin the contract read per module.
`QueryResult` declares exactly one rows member, `data`. `records` is the
below-the-adapter spelling (server envelope + client SDK) that
`ObjectStackAdapter.normalizeQueryResult` maps to `data` before returning.
`related-count-store.ts` read it AHEAD of `data` — the precedence inversion
objectui#5945 was filed about; the other six read it behind `data`, where it
was dead but still legitimised a second de-facto contract.
Measured repo-wide: no producer emits `records` at this seam. The producers
that do exist are on other seams (ViewDataProvider's own `ResolvedData`, raw
Cloud HTTP payloads, client-SDK doubles below the adapter) and are untouched.
`QueryResult` is NOT widened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#6726
QueryResult(packages/types/src/data.ts) declares exactly one rows member —data— alongsidetotal,page,pageSize,hasMore,cursor,metadata.recordsis not a member: it is the spelling the server envelope and the client SDK use, whichObjectStackAdapter.normalizeQueryResultmaps todatabefore returning. #5945 removed it from two app-shell readers; this removes it from the seven more the same producer sweep turned up, one module at a time, each with its own pin.⛔
QueryResultis not widened to blessrecords. That is a published-type change and a maintainer decision.What changed, per module
components/src/hooks/related-count-store.tsrecordstried beforedatacomponents/src/__tests__/related-count-store.contractEnvelope-6726.test.tscomponents/src/renderers/basic/data-list.tsxres?.data ?? res?.records ?? bare-arrayrenderers/basic/__tests__/data-list.contractEnvelope-6726.test.tsxcomponents/src/renderers/basic/elements.tsxelement:numberclient-side aggregate)renderers/basic/__tests__/element-number.contractEnvelope-6726.test.tsxcomponents/src/renderers/basic/record-picker.tsxrenderers/basic/__tests__/record-picker.contractEnvelope-6726.test.tsxplugin-detail/src/renderers/record-activity.tsxres?.data ?? res?.records ?? []renderers/__tests__/record-activity.contractEnvelope-6726.test.tsxplugin-detail/src/renderers/record-history.tsxrenderers/__tests__/record-history.contractEnvelope-6726.test.tsxplugin-view/src/ObjectView.tsxelse if recordsbranch of the unwrap ladder__tests__/ObjectView.contractEnvelope-6726.test.tsxrelated-count-store.tsis the one that was actively wrong: it readrecordsahead ofdata, so afind()answer carrying both would have been counted from the key the contract does not declare. Its pin asserts that case directly ({ records: 3 rows, data: 1 row }counts 1, not 3). The other six readdatafirst, so theirrecordsarm was dead — and a dead tolerant arm is still where a non-conforming producer keeps working unrejected (AGENTS.md #0.1).One repeated shape, not seven different treatments. Every module got the same two steps #5945 used — measure, then delete and pin — and each pin has the same three legs where the module has them: the contract read, the live arms, the refusal. Nothing here argues for splitting the card.
The measurement, and its control
Repo-wide over every tracked file (
git ls-files, 5,732 files — sources, tests, examples,skills/,scripts/, fixture data), for an object-literalrecords:key plus.records =assignment: 112 sites, zero of them a producer at theDataSource.find()seam.Read directly, every
find()implementation in the repo returnsdataor a bare array:ObjectStackAdapter.normalizeQueryResult(data-objectstack/src/index.ts:3246) consumes the server/SDKrecords(andvalue) envelope and returns{ data, total, page, pageSize, hasMore }.ApiDataSource.normalizeQueryResult(core/src/adapters/ApiDataSource.ts:382) readsrecordsoff the raw HTTP payload and likewise emitsdata.ValueDataSource,runner'sMockDataSource,apps/site's gallery source,examples/byo-backend-console's mock, the gantt demo source and the@object-ui/typesREST example all returndata(or a bare array).related-count-store.ts's ownProbeFnnever declaredrecordsin the first place — its return type is{ total?, data? } | unknown[] | { length? }. Only theres: anycast let the arm through.Control for the zero (a zero counts only if a term that must hit is run in the same query and does): the same sweep DOES find every legitimate
recordsproducer, on other seams —ViewDataProvider.ts(12), its test (8), the client-SDK doubles belownormalizeQueryResult(apiAccessDenied.test.ts,consoleUserPreferenceBudget.test.tsx,v3-compat.test.ts), the raw HTTP payload doubles (objectListApiDisabled-4408.test.tsx,packagedActions.test.ts), plus #5945's own twoasRecordsenvelopes. 45+ hits. A producer at thefind()seam would have been caught by the identical query.A second control lives inside each pin: the
dataleg and (where the module has one) the bare-array leg push the same rows through the same mount and both render. So arecordsleg that renders nothing rendered nothing because the envelope was refused, not because the harness was dead.Ablation — each pin red with the arm restored, green without it
Per module: restore the pre-fix expression, prove the mutation reached disk (the exact pre-fix text 0 → 1 and the post-fix text 1 → 0 — never an editor exit code), run that module's pin, restore with
git checkout HEAD --, then prove the restore (git diff HEADempty andgit hash-objectequal to the HEAD blob). Every module passed both proofs.element:number)In every case the reds are exactly the
recordsassertions and the live-arm legs stay green — the pins discriminate the deleted arm, they do not merely fail.related-count-storereds twice because it is the only module with a precedence case to assert as well.No rebuild leg is owed here, and here is why rather than an assumption: the root
vitest.config.mtsaliases every@object-ui/*specifier topackages/*/src(lines 268-284), and each pin imports its module under test by relative path. Nothing in these runs resolves through adist/, so a stale build cannot make an ablation falsely green. The console was built for the two bundle-artifact gates below, and those readdist/as intended.Exclusions — confirmed, not assumed, and untouched
app-shell/src/console/marketplace/marketplaceApi.ts:354,403— rawfetch()of Cloud's/api/v1/data/...; its own comment states the API returns{ object, records, total, hasMore }. A raw HTTP payload, not aQueryResult. Correct as it stands.app-shell/src/views/setup/packagedActions.ts:108—readDataRecordsreads the transport envelope'sFindDataResponse = { object, records, total?, hasMore? }, wrapped or bare. Its header already says these are the two forms of one response, ⛔ not a tolerant alias. Correct as it stands.core/src/data-scope/ViewDataProvider.ts—DataFetcher.fetchRecordsis declaredPromise of { records, total }andResolvedDatadeclaresrecords. Its own interface, its own seam. Correct as it stands.What moved
Nothing. No existing test was edited, renamed or deleted; the seven pins are all new files. Full suites for the three touched packages: 354 files, 3,276 tests, 0 failures.
Verification — all on
b2a4ca1c9, this branch's headRun from the repo root, never
pnpm --filter PKG test(objectui#3378 / #3288 — that re-roots vitest at the package and reports another package's files passing while running none of yours).--reporter=verbose:pnpm exec vitest runover the seven*.contractEnvelope-6726.*files —Test Files 7 passed (7)/Tests 21 passed (21).pnpm exec vitest run packages/plugin-view/ packages/plugin-detail/—Test Files 141 passed (141)/Tests 1302 passed (1302);pnpm exec vitest run packages/components/—Test Files 213 passed (213)/Tests 1974 passed (1974).pnpm --filter @object-ui/components --filter @object-ui/plugin-detail --filter @object-ui/plugin-view run type-check— 3 of 47 projects, allDone. Each package'stype-checkistsc --noEmit && tsc -p tsconfig.test.json, and--listFilesconfirms all seven new test files are in those programs (4 / 2 / 1), so the green covers them rather than merely passing beside them.pnpm lint(turbo run lint,eslint . --no-inline-config) — repo-wide,Tasks: 47 successful, 47 total,0 errors. Not narrowed.check:control-bytes(OK, 5,655 files),check:vi-mock-specifiers(OK — the ObjectView pin adds one),check:phantom-deps,check:self-import,check:side-effects-array,check:esm-specifiers,check:element-data-source-declaration,check:entry-guard,check:i18n-keys,check:i18n-drift,check:spec-symbols,check:action-forward-parity,check:designer-field-key-parity,check:icon-record-names,pnpm check— all exit 0.check-changeset-no-major.mjs(nomajordeclared — the fixed-group rule),check-changeset-overwrite.mjs(1 added, 0 modified, 0 deleted).turbo run build --filter='./packages/*'(39/39) andpnpm --filter @object-ui/console build:check:sdui-registration-pins— all 12 registrations present, 513 chunks weighed;check:eager-closure— 3174.0 KB / 3222.7 KB, 48.7 KB headroom, all per-chunk ceilings green.Out of fence — filed, not repaired here
extractRecordsreadsrecordsAHEAD ofdataonDataSource.find()answers — the shared-helper copy of the #5945/#6726 inversion, live in 9 renderers #6839 —extractRecords(core/src/utils/extract-records.ts) has the samerecords-before-dataorder and is called onfind()answers by nine renderers. Left alone: the helper is genuinely dual-seam (ViewDataProvidercalls it too, andrecordsis legitimate there), so it needs its own measurement and an architecture decision, not a copy of this change. That issue remains open.QueryResultarms survive on the seams #6726 repaired —res?.countandresults.value#6840 — two residual non-QueryResultarms on the seams this PR touched:res?.countinrelated-count-store.tsand thevaluebranch inObjectView.tsx. This card measuredrecords; those two were not measured, so they are recorded rather than deleted on numbers that do not cover them. That issue remains open.Neither is addressed by this PR, and neither is closed by it.
Generated by Claude Code
Generated by Claude Code