Found by browser verification on merged main (19a0306) with the #75 seed loaded — 186 tasks across 5 business units.
The manager's org lens is broken, and it is broken in the way that reads as a data problem rather than a bug.
Measured
/_console/apps/ai.objectstack.duly/duly_task/view/by_unit renders one collapsible group:
Business unit
▾ (empty) 100
Every row is inside it. The rows themselves are correct — right records, right count, right sort. Only the grouping did nothing.
The data is fine. Straight from the store:
select business_unit, count(*) from duly_task group by 1
9a_MClo_4ByhoRgj 61
Q2UJSlOy4ICnWmTg 86
b2usyCHDAho0owsx 31
K36wAueKC2W1fz1k 7
BUZwsjpNFv-cbyZA 1 → 186 rows, 0 nulls
And the dashboard's Not moving, by business unit chart groups the same field on the same data correctly, rendering Northgate Operations and Northgate Quality as separate bars.
Cause
The grid builds its query projection from columns alone. Captured on the page load:
GET /api/v1/data/duly_task?populate=owner&top=100&select=id,subject,status,due_date,period_key,…
→ record keys: id,subject,status,due_date,period_key,owner,source
→ business_unit: undefined
src/views/task.view.ts shares one columns const across every task lens:
const columns = [
{ field: 'subject' }, { field: 'status' }, { field: 'due_date' },
{ field: 'period_key' }, { field: 'owner' }, { field: 'source' },
];
business_unit is not in it, so it is never requested, so it is undefined on arrival, so objectui's buildSegmentLabel hits its first line — if (value === undefined || …) return '(empty)' — for all 186 rows.
Both grouped views are affected, and duty is affected twice
| View |
Groups by |
In its columns? |
task.view.ts → by_unit |
business_unit |
❌ |
duty.view.ts → grouped lens |
business_unit |
❌ |
duty.view.ts → grouped lens |
owner |
❌ |
duty's columns are name, form, frequency, source, status — neither grouping field is among them, so that view collapses on both levels.
The kanban groupByFields are fine by luck: status (board) and owner (assignments) both happen to be displayed columns.
Filed upstream
objectstack-ai/objectui#7179 — the projection should union the grouping fields rather than requiring authors to mirror them in columns. Two config keys that are separate in the schema imply grouping by a non-displayed field is supported, and it silently is not.
That is the real fix and it is not ours. Do not wait for it — the workaround here is also just better UI.
Scope
-
Add the grouping fields to the columns of both grouped views. On a by-unit view the unit column is worth showing anyway, so this is not a wart; on duty's grouped lens, add business_unit and owner. Keep the shared columns const for the lenses that want the current six, and give the grouped lenses their own list rather than widening every view to carry a column only one of them needs.
-
Add a test that fails when a view groups or kanban-groups by a field its columns do not carry. This is the actual deliverable — the fix above is three lines and the guard is what stops it coming back. It belongs in test/metadata-bindings.test.ts alongside the other binding guards, and it must be written so it would have failed on 19a0306: assert that before you fix the views, and say so in the test's comment.
Note carefully why the existing guard did not catch this, because it is the interesting part: test/metadata-bindings.test.ts resolves business_unit against duly_task's schema, and it resolves — it is a real, populated, correctly-typed lookup. Every binding was valid. The bug is a relationship between two config keys, not a dangling reference, and no amount of reference checking sees it.
-
Leave sort alone unless you can measure a failure. Sorting is applied server-side through the query, not client-side over the projection, so a sort field outside columns is very likely fine — stalled sorts by last_update_at, schedule by visible_from, and both are outside the shared six. If you can show a sort silently not happening, that is a second finding and its own card; do not fold an unmeasured guess into this one, and do not widen the test to sort without evidence.
Acceptance
by_unit renders one group per business unit, each labelled with the unit's name, counts summing to the visible row total
- the
duty grouped lens groups on both levels
- a test that fails on
19a0306 and passes after
- verified in a browser against the seeded data, not only in tests — a screenshot of the grouped view in the PR
Gates
pnpm validate && pnpm typecheck && pnpm test && pnpm build.
Found by browser verification on merged
main(19a0306) with the #75 seed loaded — 186 tasks across 5 business units.The manager's org lens is broken, and it is broken in the way that reads as a data problem rather than a bug.
Measured
/_console/apps/ai.objectstack.duly/duly_task/view/by_unitrenders one collapsible group:Every row is inside it. The rows themselves are correct — right records, right count, right sort. Only the grouping did nothing.
The data is fine. Straight from the store:
And the dashboard's
Not moving, by business unitchart groups the same field on the same data correctly, rendering Northgate Operations and Northgate Quality as separate bars.Cause
The grid builds its query projection from
columnsalone. Captured on the page load:src/views/task.view.tsshares onecolumnsconst across every task lens:business_unitis not in it, so it is never requested, so it isundefinedon arrival, so objectui'sbuildSegmentLabelhits its first line —if (value === undefined || …) return '(empty)'— for all 186 rows.Both grouped views are affected, and
dutyis affected twicecolumns?task.view.ts→by_unitbusiness_unitduty.view.ts→ grouped lensbusiness_unitduty.view.ts→ grouped lensownerduty's columns arename, form, frequency, source, status— neither grouping field is among them, so that view collapses on both levels.The kanban
groupByFields are fine by luck:status(board) andowner(assignments) both happen to be displayed columns.Filed upstream
objectstack-ai/objectui#7179 — the projection should union the grouping fields rather than requiring authors to mirror them in
columns. Two config keys that are separate in the schema imply grouping by a non-displayed field is supported, and it silently is not.That is the real fix and it is not ours. Do not wait for it — the workaround here is also just better UI.
Scope
Add the grouping fields to the columns of both grouped views. On a by-unit view the unit column is worth showing anyway, so this is not a wart; on
duty's grouped lens, addbusiness_unitandowner. Keep the sharedcolumnsconst for the lenses that want the current six, and give the grouped lenses their own list rather than widening every view to carry a column only one of them needs.Add a test that fails when a view groups or kanban-groups by a field its
columnsdo not carry. This is the actual deliverable — the fix above is three lines and the guard is what stops it coming back. It belongs intest/metadata-bindings.test.tsalongside the other binding guards, and it must be written so it would have failed on19a0306: assert that before you fix the views, and say so in the test's comment.Note carefully why the existing guard did not catch this, because it is the interesting part:
test/metadata-bindings.test.tsresolvesbusiness_unitagainstduly_task's schema, and it resolves — it is a real, populated, correctly-typed lookup. Every binding was valid. The bug is a relationship between two config keys, not a dangling reference, and no amount of reference checking sees it.Leave
sortalone unless you can measure a failure. Sorting is applied server-side through the query, not client-side over the projection, so asortfield outsidecolumnsis very likely fine —stalledsorts bylast_update_at,schedulebyvisible_from, and both are outside the shared six. If you can show a sort silently not happening, that is a second finding and its own card; do not fold an unmeasured guess into this one, and do not widen the test tosortwithout evidence.Acceptance
by_unitrenders one group per business unit, each labelled with the unit's name, counts summing to the visible row totaldutygrouped lens groups on both levels19a0306and passes afterGates
pnpm validate && pnpm typecheck && pnpm test && pnpm build.