Skip to content

fix(fields,plugin-detail,plugin-list,app-shell,console): read count/value answers as the contract declares them (objectui#6917) - #8624

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-6917-queryresult-value-count-arms
Sep 8, 2026
Merged

fix(fields,plugin-detail,plugin-list,app-shell,console): read count/value answers as the contract declares them (objectui#6917)#8624
os-justin merged 1 commit into
mainfrom
claude/issue-6917-queryresult-value-count-arms

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #6917

Six sites outside the fences of objectui#6726 / #6840 / #6839 still carried a count or value
arm. Each got its own in-repo producer census, with the control sitting on the producer→consumer
JOIN
— because #6840's zero is seam-local and is not transferable. Arms were deleted only on a
zero
; the one site whose census came back non-zero kept its arm and got a precedence repair
instead.

QueryResult is not widened to bless count or value.


Two premises of the card, falsified

1. Three of the six sites are not on the DataSource.find() seam at all.

The card files all six as "the same arm #6840 removed", i.e. a non-QueryResult key at the
DataSource.find() seam. app-shell's AppHeader, and the console's PublicFormsPage and
FlowRunsPage, call client.meta.getItems(type), whose envelope is { type, items: [...] }
(or a bare array on the ADR-0037 preview path). QueryResult never reaches them, and data is not
a member of that envelope either — MetadataProvider.extractItems pins { data: […] } → [], and
MetadataService.getItems reads items and nothing else. The contract member there is items,
not data.

That is the card's own central rule turned back on the card, so those three were measured on
their own join, not the one the card named.

2. Arm A's census is NON-ZERO — so the arm was kept.

packages/fields' lookup chip read result?.value || result?.data || []value ahead of data.
The inversion is real and confirmed. But value is live at that seam: 8 in-repo find()
doubles emit { value: [...] }. Deleting the arm is exactly the plausible wrong fix the card's
non-regression axis names, so only the rank changed. Per the card's route this is a stop-and-
report on that site; it is reported here rather than deleted.

Censuses

Both run by a bracket-balancing extractor over all 4406 tracked .ts/.tsx files
packages/**, apps/**, examples/**, e2e/**, scripts/**, so apps/console is inside the
population. Controls are the same extraction on the same population, differing only in the key,
so a lit control proves the pass reaches the join rather than merely finding the word.

Seam 1 — DataSource.find()QueryResult

key producers
CONTROL data 312 / 592
CONTROL total 150 / 592
SUBJECT count 0 / 592
SUBJECT value 8 / 592 (after adjudication)
SUBJECT value and data both, as envelope members 0

The last row is why the inversion is a potentially answers-wrong, not an answers-wrong — which is
triage's own reason for grading this p2 and not p1.

Seam 2 — client.meta.getItems() → metadata envelope

key producers
CONTROL items 18 / 28
SUBJECT value 0 / 28

Guards against a false zero

  • Named controls. Specific producers that must be located. The first draft of the extractor
    stopped at vi.fn() and never saw .mockResolvedValue({…}); a second draft missed ES6 shorthand
    { items }. Both were caught by a named control going dark, and both are fixed — data rose
    312 from 138 once they were.
  • Superset sweep. Every file holding a producer is also grepped whole, so a shape assembled
    outside a producer body still surfaces. All 25 count:-bearing files were adjudicated by hand:
    every one is the DataSource's own sibling count() method, a row field, an i18n interpolation
    parameter, an aggregate function name, React state, a DOM assertion, a comment, or finding(components,plugin-view): two more non-QueryResult arms survive on the seams #6726 repaired — res?.count and results.value #6840's own
    refusal pin. Same for the 6 value: files on seam 2 (filter values, select options, a DOM helper
    parameter, a storage shim).
  • '@odata.count' is a different keyres?.count cannot read it — so it does not answer this
    question. The 2 producers emitting it are the plugin-grid fixtures repaired below.

What changed

site seam key census action
fields/src/index.tsx find() value 8 — non-zero precedence fixed, arm kept; now delegates to @object-ui/core's extractRecords
plugin-detail/…/record-reference-rail.tsx find() count 0 arm deleted
plugin-list/src/ListView.tsx find() count 0 arm deleted
app-shell/…/AppHeader.tsx meta.getItems() value 0 arm deleted
console/…/PublicFormsPage.tsx meta.getItems() value 0 arm deleted
console/…/FlowRunsPage.tsx meta.getItems() value 0 arm deleted

Plus the two plugin-grid doubles that answered { value: [], '@odata.count': 0 } while
ObjectGrid reads result.data / result.total — fixtures that read as though they supplied rows
and supplied none. Repaired to { data: [], total: 0 }.

Pins — one per module

*.contractEnvelope-6917.*, following #6840's form: the header carries the measurement, the live
arms stay pinned beside the dead one
, and a precedence case is written only where an inversion
actually exists. 29 new tests.

Reverse verification — every pin observed RED, classified per-test from vitest's JSON reporter
(not the text reporter). Every failure is an [assertion]; zero suite deaths. Each leg proved
its mutation reached disk (injected/removed text counted before and after) and each restore was
proved by content — git diff HEAD empty and all blob hashes equal to HEAD.

leg mutation result
fields restore the inverted ladder 1 of 5 red — only the both-members case. Exactly right: nothing else can see precedence
rail restore the count arm 2 red (expected '7' to be '0', expected '7' to be '2')
listview restore the count arm 2 red (expected '7 records' to be '3 records', '99 records')
appheader restore the value arm 1 red
console restore the value arm on both pages 2 red
grid teach ObjectGrid to read value 1 red
caricature extractRecords always returns [] 4 of 5 red — the live-arm cases catch it

In every leg the "always correct" control case (total outranks count; items still read) stayed
green, so each red is a reading of this change and not of a module that stopped working.

⚠️ The grid leg first came back green when the two fixtures were reverted — correctly, because
that pin's subject is ObjectGrid's read, not fixtures it does not use. The leg was retargeted at
the real subject. Stated plainly: reverting those two fixtures alone is not detected by any test,
by construction — a fixture is an input to a suite, not its subject. What is guarded is the contract
that makes the repair correct.

Verification

  • packages/fields/ packages/plugin-detail/ packages/plugin-grid/ packages/plugin-list/
    479 files, 5718 tests passed
  • packages/app-shell/ apps/console/746 files, 7414 passed, 1 skipped
  • turbo run type-check over the six packages — 41/41 successful (each package echoes
    tsc --noEmit && tsc -p tsconfig.test.json, so the new pins are typechecked too)
  • eslint . whole repo — 4539 files, 0 errors (12319 pre-existing warnings; no
    --max-warnings, no warning ratchet). Not a narrowed run: the full population was linted.
  • check-changeset-presence ✅ · check-changeset-no-major ✅ · check-control-bytes
    (6813 files scanned)

All at c71bbff9f.

Out of scope — reported, not touched

Line numbers, re-located by content

fields:250 unchanged · rail:196 unchanged · ListView 1816 → 2077 · AppHeader 182 → 186 ·
PublicFormsPage 134 → 135 · FlowRunsPage:155 unchanged.

Fence collisions re-checked against the live open-PR list: zero open PRs touch any of the six
files (app-shell's two open PRs, #8615 and #8164, are the flow-node inspector and record-scope
conditional formatting).


🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…alue answers as the contract declares them

Six sites outside the fences of objectui#6726 / #6840 / #6839 still carried a
`count` or `value` arm. Each got its OWN in-repo producer census, with the
control sitting on the producer->consumer JOIN rather than on the word --
objectui#6840's zero is seam-local and is not transferable (the same sweep read
0 producers for `value` at one seam and 5 at another in one pass).

ARM A -- a real precedence inversion, repaired WITHOUT deleting the arm.
`packages/fields`' lookup chip read `result?.value || result?.data || []`:
`value` ahead of `data`, the one rows member `QueryResult` declares. It now
delegates to `@object-ui/core`'s `extractRecords` -- same accepted set (bare
array, `data`, `value`), contract order. The `value` arm STAYS: its census found
8 live `find()` doubles emitting `{ value: [...] }` (3 plugin-kanban, 3
plugin-calendar, 2 plugin-grid), so deleting it -- the plausible wrong fix --
would have broken them. 0 producers emit BOTH members, which is why this was a
"potentially answers wrong" and graded p2 rather than p1.

FIVE DEAD ARMS deleted, each on its own measured zero:
  `count` at the DataSource.find() seam -- plugin-detail's reference rail and
  plugin-list's ListView. 0 / 592 find() producers emit `count`; controls `data`
  (312) and `total` (150) lit on the same pass, plus a superset sweep over all
  418 producer-bearing files adjudicated to zero envelope members.

  `value` at the client.meta.getItems() seam -- app-shell's help menu, and the
  console's Public Forms and Flow Runs pages. These three do NOT sit on the
  find()/QueryResult seam at all, contrary to how the card described them, so
  they were measured on their own join: 0 / 28 meta.getItems producers emit
  `value`; control `items` (18) lit. The canonical readers of that envelope
  (`MetadataProvider.extractItems`, `MetadataService.getItems`) have never had a
  `value` arm either.

`QueryResult` is NOT widened to bless `count` or `value` -- a published-type
change and the maintainer's call, the floor #6726 / #6840 / #6839 all held.

One refusal pin per module (`*.contractEnvelope-6917.*`), each keeping the live
arms green beside the deleted one, and -- only where an inversion existed -- a
case feeding both members with different contents, the sole input that separates
the two orders.

Also repaired: two plugin-grid test doubles answered
`{ value: [], '@odata.count': 0 }` while ObjectGrid reads `result.data` /
`result.total`. Inert only while the arrays were empty; the first row put in one
would have been silently dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3477.1 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-apUOcgqI.js
Status PASS

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.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.93KB 114.12KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 243.73KB 61.53KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 250.71KB 64.81KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.30KB 56.63KB
plugin-kanban (index.js) 55.44KB 15.73KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-justin
os-justin marked this pull request as ready for review September 8, 2026 17:58
@os-justin
os-justin enabled auto-merge September 8, 2026 17:59
@os-justin
os-justin added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit bd0376d Sep 8, 2026
35 checks passed
@os-justin
os-justin deleted the claude/issue-6917-queryresult-value-count-arms branch September 8, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants