Uh oh!
There was an error while loading. Please reload this page.
Fetch the fields the grouped grids group by, and guard it - #85
Conversation
`by_unit` rendered one `(empty)` group holding all 186 rows, and `duty`'s grouped lens collapsed on both of its levels. The rows were right; only the grouping did nothing, and nothing errored. The grid builds its query projection from `columns` alone — measured on the seeded app, the request was `select=id,subject,status,due_date,period_key,owner,source`, so `business_unit` arrived `undefined` and the renderer's `buildSegmentLabel` returned `(empty)` for every row. Both grouped lenses now carry their own grouping fields as columns; the lenses that want the shared six are untouched. Filed upstream as objectstack-ai/objectui#7179 — the projection should union the grouping fields rather than making authors mirror them. Not waiting on it: on a by-X view the X column is worth showing anyway. The guard is the deliverable. `test/metadata-bindings.test.ts` gains a second, independent stopgap: a grouped grid must carry its grouping fields in `columns`. The existing walk cannot see this — it resolves `business_unit` against the schema and it RESOLVES; the defect is a relationship between two config keys, not a dangling reference. Proven red on 19a0306 before the fix, with the transcript recorded in the test. Scope is measured, not assumed: ablating each group key one at a time against a booted app showed `kanban` / `gantt` / `timeline` `groupByField` are each unioned into the projection by their own adapter, so the guard covers `grouping.fields[]` only and inventories the rest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren
commented
Sep 1, 2026
Reviewed — merging. The deviation from my scope is a correction to my card, and it is right.Gates, re-run by me on I wrote in scope item 2 that "the kanban So the grid's Two more things worth recording:
I have posted your kanban/gantt/timeline measurement to objectui#7179, since I had told the platform team those were worth checking and you have now checked them. #86 (page-scoped group counts) is a real finding and correctly out of scope here — this card's own acceptance treated the counts as page-scoped, so fixing it would have been scope creep. Triaging it separately. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
`by_unit` carried no filter, so it rendered all 186 tasks — 151 of them `done` — against a `top=100` request. The grid groups client-side over the fetched page and computes its per-group counts over that same array, so every group header read a page slice as a total and one of the five business units had no group on the screen at all, with nothing saying a unit was missing. Measured in a browser on the seeded app, same server and session, with only this filter reverted: before Operations 33 · Plant 3 · Quality 46 · Riverside 18 = 100, "Showing first 100 records", Central Office ABSENT after Central Office 1 · Operations 7 · Plant 1 · Quality 18 · Riverside 6 = 33 records, five groups, no truncation It is also the better lens on its merits — nobody wants a by-unit breakdown of work that finished six months ago. Not a bigger page size, which moves the cliff instead of removing it. The residual is stated rather than papered over: this stays structurally page-scoped, so the view's `description` says the dashboard is the authoritative by-unit surface and this lens is for browsing. That description is served but NOT rendered today — the console's ObjectView relay never copies a per-view `description` onto the ListView schema — filed as objectstack-ai/objectui#7199 and said plainly in the view comment so nobody believes the caveat is in front of users yet. The guard pins the decision and inventories the other grouped grids, in the same file and idiom as #85's — `catalog_tree` is measured at 31 duties and deliberately not required to carry a filter. Upstream: objectstack-ai/objectui#7189. Closes#86 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Fixes#76
Both grouped grids bucketed by a field they never fetched.
by_unitrendered one(empty)group holding all 186 rows;duty's grouped lens collapsed on both levels. The rows were right, nothing errored, and all four gates were green.Cause, re-measured on this branch's own boot
The grid builds its query projection from
columnsalone. Captured off the page load at19a0306:business_unitwas thegroupingfield and not a column, so it was never requested, sobuildSegmentLabeltook its first line —if (value === undefined || …) return '(empty)'— for every row. Read off the shipped console bundle,$selectis[id, ...columns, ...fields referenced by action / conditional-formatting predicates];groupingis not in that union.Upstream: objectstack-ai/objectui#7179 — the projection should union the grouping fields rather than making authors mirror them in
columns. Not waiting on it, and this is not a wart: on a by-X view the X column is worth showing anyway.1. The views
task.view.ts › by_unit— the shared six plusbusiness_unit([...columns, …], the same idiomstalledalready uses). The other five task lenses keep the shared const untouched.duty.view.ts › catalog_tree— its own list gainsbusiness_unitandowner, listed in the grouping's own order so the row reads the way the hierarchy nests.2. The guard — the actual deliverable
test/metadata-bindings.test.tsgains a second, independent stopgap, deleted when objectui#7179 lands and not with the reference walk above it (that one goes with objectstack#14105 / #14107 / #14108).Why the existing guard could not catch this: it resolves
business_unitagainstduly_task's schema and it resolves — a real, populated, correctly-typed lookup. Every binding was valid. The defect is a relationship between two config keys, not a dangling reference.Proven red first. On
19a0306with the views unfixed,npx vitest run test/metadata-bindings.test.tsexited 1:The transcript is recorded in the test's own comment, and synthetic fixtures pin both directions permanently (fires / does not fire, multi-level, bare-string column shorthand).
One measured deviation from the card's scope
The card asks the guard to cover kanban-grouping too, on the reading that the kanban board is "fine by luck" because its group field happens to be displayed. It is not luck, and
statusis not one of the board'scolumns. Ablated against a booted app one leg at a time, each mutation confirmed on disk and reverted through a restore trap:selectkanban.groupByField→business_unit…,source,business_unit,statusbusiness_unitadded tokanban.columnsgantt.groupByField→business_unit…,due_date,business_unittimeline.groupByField: business_unit…,business_unit,due_dateSo
kanban/gantt/timelinegroupByFieldare each unioned into the projection by their own adapter — the grid'sgroupingblock is the one that is not, which is exactly the gap objectui#7179 names. The guard therefore coversgrouping.fields[]only; widening it to three keys that measurably work would be the same mistake the card warns against forsort. The other group keys are still inventoried by the walk, so adding one fails a tripwire and lands in front of a human. Legs A/B also pin where a field must go:kanban.columnsis the card face (cardFields), never the projection.3.
sort— left alone, as instructedNo measurement showed a sort failing, and sorting is applied server-side (
$orderbyin the same request), so asortfield outsidecolumnsis a different mechanism. Not touched, not guarded.Browser verification
objectstack devon port 3100 from this worktree, #75 seed (186 tasks, 31 duties, 5 business units), logged in as admin.Before — one
(empty)bucket holding every row:After —
by_unit, one group per unit, counts 33 + 3 + 46 + 18 = 100 = the visible row total (the seed's fifth unit has no rows on page 1 of 100):After —
duty's lens grouping on both levels (Ardenline Group → Nadia Ilves, Northgate Operations → Marek Dvorak 2 / Sami Okonkwo 3):Post-fix requests:
select=id,subject,status,due_date,period_key,owner,source,business_unitandselect=id,name,form,frequency,business_unit,owner,source,status. Zero(empty)labels on either lens, no console errors, no 4xx.The screenshots live on the throwaway branch
claude/issue-76-evidenceso no binaries enter this diff ormain— delete it whenever.Gates
All four green at
df6b114(this PR's head):No changeset: this repo has no
.changeset/directory ingit log --all, no@changesets/*dependency and no changeset script — creating one would mint a mechanism nothing here reads.Generated by Claude Code
Generated by Claude Code