Skip to content

docs(ui): the react-pages live-data sample reads result.records, which is always undefined — and its Callout misstates what a dropped option does #10469

Description

@os-zhuang

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.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions