Skip to content

Commit ddc14ec

Browse files
committed
Delete the unreachable Array.isArray limb at all three adapter.find sites
`ObjectStackAdapter.find()` cannot resolve to an array. Re-derived on the pinned objectui sha (9602dc8) and on objectui `origin/main`: find() has two object-literal returns (`{ data: [], total: 0 }` for a memoized 404 and for a fresh non-denial 404), two `normalizeQueryResult(...)` returns, and an inflight `return existing` that hands back a promise from that same set. Both of `normalizeQueryResult`'s branches return an object literal with exactly `data, total, page, pageSize, hasMore` -- the first one WRAPS a bare array response into it. So no `Array.isArray(<find result>)` limb can ever be taken. Behaviour-preserving, like the `?? records` deletion beside it: `.data` was already read first and always won. What goes is a shape the producer cannot emit, in the sample a customer (and a coding agent) copies from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
1 parent d7e8f3e commit ddc14ec

3 files changed

Lines changed: 15 additions & 6 deletions

File tree

‎content/docs/ui/react-pages.mdx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ function Page() {
176176
$filter: ['status', '!=', 'paid'],
177177
$top: 200,
178178
});
179-
const records = result?.data ?? (Array.isArray(result) ? result : []);
180-
if (alive) setRows(records);
179+
if (alive) setRows(result.data);
181180
})();
182181
return () => { alive = false; };
183182
}, [adapter]);
@@ -186,6 +185,11 @@ function Page() {
186185
}
187186
```
188187

188+
`find()` always resolves to the same envelope — a `QueryResult` carrying the rows
189+
under `data`. A backend that answers with a bare array is folded into that envelope
190+
before your code sees it, so `result.data` is the only row shape a page is ever handed:
191+
read it directly, with no fallback for a shape the adapter cannot produce.
192+
189193
<Callout type="warn">
190194
The `$` prefixes are load-bearing. An unprefixed `top:` or a `filters:` key is not a
191195
query option — it is silently dropped, and the query runs as if you had not written

‎examples/app-showcase/src/ui/pages/crm-workbench.page.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ function Page() {
3737
// silently stuck at 0 even though the ListView beside them showed the same
3838
// rows. Read .data: it is the only row shape QueryResult declares, so a
3939
// tolerant '.data || .records' alias would render correctly while
40-
// teaching a spelling the producer cannot emit.
40+
// teaching a spelling the producer cannot emit. There is no array
41+
// shape to test for either: every find() path resolves to that same
42+
// object envelope, and normalizeQueryResult WRAPS a bare array
43+
// response into it, so an 'Array.isArray(all)' limb can never be taken.
4144
//
4245
// The cap is $top, not 'limit': QueryParams declares only $-prefixed keys
4346
// and the adapter copies only those, so a bare 'limit' is dropped without
@@ -48,7 +51,7 @@ function Page() {
4851
// "Active" stays a per-row verdict over the 200 rows actually fetched;
4952
// an exact one would need its own filtered count query.
5053
const all = await adapter.find('showcase_project', { $top: 200 });
51-
const rows = Array.isArray(all) ? all : (all && all.data) || [];
54+
const rows = all?.data ?? [];
5255
const total = typeof (all && all.total) === 'number' ? all.total : rows.length;
5356
setStats({ total, active: rows.filter((r) => r.status === 'active').length });
5457
} catch (e) { console.warn('[CRM Workbench] failed to refresh stats', e); }

‎examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,16 @@ function Page() {
7575
// applied, is the server's real count over the same $filter rather than
7676
// the page length. Counting data.length under a cap is how a KPI starts
7777
// under-reporting in silence; count 'total' and the cap stays a fetch
78-
// bound instead of a lie about the business.
78+
// bound instead of a lie about the business. That envelope is the ONLY
79+
// shape find() resolves to -- normalizeQueryResult WRAPS a bare array
80+
// response into it -- so there is no array form to test for either.
7981
React.useEffect(() => {
8082
let alive = true;
8183
(async () => {
8284
if (!adapter || !sel) { setRelated({ projects: 0, invoices: 0, openInvoices: 0, capped: false }); return; }
8385
const pr = await adapter.find('showcase_project', { $filter: ['account', '=', sel], $top: 500 });
8486
const iv = await adapter.find('showcase_invoice', { $filter: ['account', '=', sel], $top: 500 });
85-
const rows = (res) => (Array.isArray(res) ? res : (res && res.data) || []);
87+
const rows = (res) => res?.data ?? [];
8688
const count = (res, list) => (typeof (res && res.total) === 'number' ? res.total : list.length);
8789
const projects = rows(pr);
8890
const invoices = rows(iv);

0 commit comments

Comments
 (0)