Found while fixing #10288 (the same two defects in examples/app-showcase). Filing
rather than fixing: content/docs/** pulls in a different gate family than that card's
diff (check:doc-anchors, check:docs-audit-scope, check:docs-redirects,
check:published-readme-links, check:role-word, plus the {/* os:check */} verified-
sample convention), and the Callout's second half needs a wording decision, not a
mechanical edit.
1. The sample renders an empty list
content/docs/ui/react-pages.mdx, the "Live data" sample (L143-148):
constresult=awaitadapter.find('showcase_invoice',{$filter: ['status','!=','paid'],$top: 200,});constrecords=Array.isArray(result) ? result : (result&&result.records)||[];The query options are right. The result read is not. useAdapter() returns
ObjectStackAdapter, whose find() resolves to a normalized QueryResult — objectui
packages/types/src/data.ts declares data / total / page / pageSize /
hasMore / cursor, and normalizeQueryResult (objectui
packages/data-objectstack/src/index.ts) returns exactly that shape. There is no
records key. result.records is undefined on every call, so records falls to []
and the documented sample renders an empty <ul> forever, with no error.
This is not theoretical: the identical read was fixed once already 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 was still live in
renewals-pipeline.page.ts until #10288. The doc is the third instance, and it is the
one a customer copies from.
Fix: result.data, with the array arm kept. Every objectui consumer of this contract
spells it res?.data ?? res?.records ?? … (data-list.tsx, record-picker.tsx,
ObjectRefField.tsx, record-history.tsx, …) — canonical first, the legacy spelling as
a fallback, never alone.
2. The Callout names the wrong consequence
Directly below the sample:
An unprefixed top: or a filters: key is not a query option — it is silently
dropped, and the query comes back unfiltered or with the default page size. There
is no error.
The "unfiltered" half is right. The page-size half is backwards. The GET list route has
no default page size, so an absent top returns the ENTIRE match set — stated in
packages/client/src/index.ts ("The GET list route has no default page size, so an
absent top returns the ENTIRE match set"), pinned in packages/client/src/client.test.ts
(#6485), and measured by packages/objectql/src/protocol-unknown-query-param.test.ts's
baseline — no params returns every row (10 rows seeded, total: 10 with no params).
So the real failure mode of a dropped top is an unbounded read, not a truncated
one — the cap the author wrote never happens, and the page pulls every matching row over
the wire. That is a different thing to warn an author about, and the current wording
sends anyone debugging it looking for pagination that is not there.
Suggested wording: "…it is silently dropped. The query then runs as if you had not
written it: an unprefixed filter: comes back unfiltered, and an unprefixed top:
comes back with every matching row, because the list route has no default page size.
There is no error."
Acceptance
- The sample reads
result.data (array arm preserved) and, run against a real adapter,
renders rows. - The Callout states the unbounded-read consequence, sourced from the client/protocol
behaviour above rather than from the old wording. - The named docs gates stay green.
Found while fixing #10288 (the same two defects in
examples/app-showcase). Filingrather than fixing:
content/docs/**pulls in a different gate family than that card'sdiff (
check:doc-anchors,check:docs-audit-scope,check:docs-redirects,check:published-readme-links,check:role-word, plus the{/* os:check */}verified-sample convention), and the Callout's second half needs a wording decision, not a
mechanical edit.
1. The sample renders an empty list
content/docs/ui/react-pages.mdx, the "Live data" sample (L143-148):The query options are right. The result read is not.
useAdapter()returnsObjectStackAdapter, whosefind()resolves to a normalizedQueryResult— objectuipackages/types/src/data.tsdeclaresdata/total/page/pageSize/hasMore/cursor, andnormalizeQueryResult(objectuipackages/data-objectstack/src/index.ts) returns exactly that shape. There is norecordskey.result.recordsisundefinedon every call, sorecordsfalls to[]and the documented sample renders an empty
<ul>forever, with no error.This is not theoretical: the identical read was fixed once already in
examples/app-showcase/src/ui/pages/crm-workbench.page.ts, whose comment records thesymptom 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 was still live in
renewals-pipeline.page.tsuntil #10288. The doc is the third instance, and it is theone a customer copies from.
Fix:
result.data, with the array arm kept. Every objectui consumer of this contractspells it
res?.data ?? res?.records ?? …(data-list.tsx,record-picker.tsx,ObjectRefField.tsx,record-history.tsx, …) — canonical first, the legacy spelling asa fallback, never alone.
2. The Callout names the wrong consequence
Directly below the sample:
The "unfiltered" half is right. The page-size half is backwards. The GET list route has
no default page size, so an absent
topreturns the ENTIRE match set — stated inpackages/client/src/index.ts("The GET list route has no default page size, so anabsent
topreturns the ENTIRE match set"), pinned inpackages/client/src/client.test.ts(#6485), and measured by
packages/objectql/src/protocol-unknown-query-param.test.ts'sbaseline — no params returns every row(10 rows seeded,total: 10with no params).So the real failure mode of a dropped
topis an unbounded read, not a truncatedone — the cap the author wrote never happens, and the page pulls every matching row over
the wire. That is a different thing to warn an author about, and the current wording
sends anyone debugging it looking for pagination that is not there.
Suggested wording: "…it is silently dropped. The query then runs as if you had not
written it: an unprefixed
filter:comes back unfiltered, and an unprefixedtop:comes back with every matching row, because the list route has no default page size.
There is no error."
Acceptance
result.data(array arm preserved) and, run against a real adapter,renders rows.
behaviour above rather than from the old wording.