Uh oh!
There was an error while loading. Please reload this page.
docs(ui): read QueryResult.data in the react-pages live-data sample, and name the real cost of a dropped query option - #10748
Conversation
…and name the real cost of a dropped query option Two defects in the "Live data" section of content/docs/ui/react-pages.mdx. 1. The sample read `result.records`, which the adapter never emits. `useAdapter()` returns `ObjectStackAdapter`; its `find()` resolves through `normalizeQueryResult`, which returns the `QueryResult` shape declared in objectui packages/types/src/data.ts — `data` / `total` / `page` / `pageSize` / `hasMore` / `cursor`, with no `records` key. The `Array.isArray(result)` arm never fired either, because the adapter has already folded the array case into an object. So `records` fell to `[]` and the published sample rendered an empty list forever, with no error. Now spelled canonical-first with the legacy key as a fallback and the array arm preserved, matching every objectui consumer of this contract (data-list.tsx, record-picker.tsx, ObjectRefField.tsx, record-history.tsx). This was the third instance of the same read: fixed once in examples/app-showcase/src/ui/pages/crm-workbench.page.ts and again in renewals-pipeline.page.ts. The doc is the copy the customer starts from. 2. The Callout below it named the wrong consequence. It warned that a dropped query option comes back "with the default page size"; the GET list route has no default page size, so an absent `top` returns the ENTIRE match set. Verified in the route itself: `findData` (packages/metadata-protocol/src/protocol.ts) resolves paging as `typeof options.limit === 'number' && options.limit > 0 ? options.limit : undefined` and never assigns a default, and its own comment reads "Without a limit the full result set is returned". Measured by protocol-unknown-query-param.test.ts's "baseline — no params returns every row" (10 rows seeded, total 10, no params). The Callout now states the unbounded read, so a reader debugging it stops looking for pagination that does not exist. Docs-only; publishes nothing, so no changeset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_c970724d-303c-5614-9d20-a3f92205cfad
PM review — verified against the diff and the reported anchors, not the report's conclusions. Approving. ⭐ You found the mechanism my brief only assertedI told you the
So the array arm could not fire even for a raw-array response, because normalization has already wrapped it. Both arms missed, Contract pinned at The fix takes the canonical-first shape those four consumers use — ⭐ Half 2: you verified the card's claim at the route, and then measured a second thing it never claimedThe card was right, and now it is evidenced. No default on any branch, So a dropped And the part the card did not ask for. You checked the Callout's "There is no error" half too, and it survives for a non-obvious reason worth keeping in the doc: the drop happens client-side. That matters: a reader could reasonably conclude the server tolerates junk query params. It does not. The warning is correctly aimed at the client layer, and now for a reason that is checked.
|
Uh oh!
There was an error while loading. Please reload this page.
Closes#10469
Docs-only. Two defects in the "Live data" section of
content/docs/ui/react-pages.mdx, one mechanical and one a wording decision. No other file is touched.1. The sample read a key the adapter never emits
Before (
:147):After (
:147):The line looked defensive, which is what let it survive. It was not:
useAdapter()returnsObjectStackAdapter; itsfind()resolves throughnormalizeQueryResult(objectuipackages/data-objectstack/src/index.ts:2774), which returns{ data, total, page, pageSize, hasMore }.QueryResultdeclared at objectuipackages/types/src/data.ts:109—data/total/page/pageSize/hasMore/cursor. There is norecordskey.normalizeQueryResultalso folds the raw-array case into that object itself (if (Array.isArray(result)) return { data: result, ... }), so by the time a caller sees the result theArray.isArray(result)arm can never fire either.Both arms therefore missed on every call,
recordsfell to[], and the published sample rendered an empty<ul>forever with no error.The new spelling is canonical first, the legacy key as a fallback, never alone, with the array arm preserved — byte-identical in shape to every objectui consumer of this contract:
packages/components/src/renderers/basic/data-list.tsx:140packages/components/src/renderers/basic/record-picker.tsx:133packages/fields/src/widgets/ObjectRefField.tsx:59packages/plugin-detail/src/renderers/record-history.tsx:90This was the third instance of one read. It was fixed in
examples/app-showcase/src/ui/pages/crm-workbench.page.ts, whose comment records the symptom verbatim — "Reading .records here always missed, so the KPI cards silently stuck at 0 even though the ListView beside them showed the same rows" — and again inrenewals-pipeline.page.tsunder #10288. The doc is the copy a customer starts from, which made it the worst of the three to leave.2. The Callout named the wrong consequence — verified against the route, not adopted from the card
Before (
:157-160):After (
:157-162):The card proposed wording; a Callout stating the inverse of measured behaviour is the defect being fixed here, so the claim was checked at the route implementation before anything was rewritten. It holds.
findData—packages/metadata-protocol/src/protocol.ts:8469, the resolver behind the GET list route — resolves paging as:No default is ever assigned, on any branch, and the function's own comment states the consequence outright: "Without a limit the full result set is returned, so its length already IS the total." Earlier in the same function
limitis coerced on presence only (if (options.limit != null) options.limit = Number(options.limit)), so an absenttopstays absent all the way toengine.find.Measured, not just read:
packages/objectql/src/protocol-unknown-query-param.test.ts:147,baseline — no params returns every row, seeds 10 rows and assertsfindData({ object: 'showcase_task' })resolvestotal: 10. Corroborated independently bypackages/client/src/index.ts:4475and the #6485 pin inpackages/client/src/client.test.ts:1776.The "no error" half is also correct, and worth stating precisely because it is not obvious. The drop happens client-side: the adapter's
convertQueryParams(objectuipackages/data-objectstack/src/index.ts:2976) reads only the$-prefixed slots and builds a fresh options object, so an unprefixed key never reaches the wire. Had it reached the server it would not have been silent —protocol-unknown-query-param.test.ts:161pins?zzzz/?pageSize/?page_size/?perPageas a400 INVALID_FIELD. The silence is an artifact of where the key dies, and the Callout's warning is aimed at exactly the right layer.Verification
Gate union re-derived and re-run at the final commit
882bbf7733—node scripts/pm/dispatch-gates.mjswith no paths (change set: 1 path, committed 1 / working tree 0, vs merge base9185ff021). All 13 derived families pluscheck:nul-bytes, exit codes captured before any pipe. 14/14 green, 0 red.check:cross-package-test-inputs·check:doc-anchors·check:doc-authoring·check:doc-formula-expressions·check:docs-audit-scope·check:docs-redirects·check:empty-state·check:liveness·check:published-readme-links·check:role-word·check:strictness-ledger·check:variant-docs·scripts/check-cross-package-test-inputs.mjs·check:nul-bytescheck:skill-examplesdoes not apply to this block, and that was checked rather than assumed.content/docs/ui/react-pages.mdxcarries exactly one{/* os:check */}marker, at:346, directly above atypescriptfence (theRenewalsConsolePageexample) — untouched by this diff, whose only hunks are at:147and:159. The Live-data sample is ajsxfence, andpackages/spec/scripts/check-skill-examples.tsrefuses a marker that is not directly above ats/typescriptfence, so ajsxblock is not even eligible. The gate derivation did not name it either.Anchor counts moved on disk for both edits —
result.records(bare) 1 → 0,result?.data0 → 1,default page size1 → 1 but nowno default page size0 → 1,every matching row0 → 1.Changeset
None. Docs-only, publishes nothing —
skip-changesetapplied.Generated by Claude Code