Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Scope the by-unit lens to open work - #91

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work
Sep 1, 2026
Merged

Scope the by-unit lens to open work#91
os-warren merged 2 commits into
mainfrom
claude/issue-86-by-unit-open-work

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#86

Implements the triage decision on the card, not the three options in its body: the lens is paging because it is showing the wrong rows, so it gets a scope rather than a bigger page.

The change

duly_task › by_unit carried no filter and rendered all 186 tasks, 151 of them done, against a top=100 request. It now carries status in ('open','in_progress').

Two things beyond the one-line filter, and they are the reason the card exists:

  1. The view comment says the filter is load-bearing for grouping completeness — not a scope preference, and not a default worth inheriting. Someone will widen this later; the comment plus the guard are what stop that being silent.
  2. The residual is stated rather than papered over (below).

Page size deliberately not raised: it moves the cliff instead of removing it, and hides the next occurrence.

Measured, in a browser, on the seeded app

Same server, same session, same seed — only this filter reverted between the two runs (git checkout origin/main -- src/views/task.view.ts, restored through a trap). Console at /_console/apps/duly_app/duly_task/view/by_unit.

Before — four groups, and the counts are page slices:

group headershownin store
Northgate Operations3361
Northgate Plant37
Northgate Quality4686
Riverside Plant1831
Central Officeno group at all1

Footer: 100 records · Showing first 100 records. More data may be available. — reproducing the card's measurement exactly.

After — five groups, every count true:

group headercount
Central Office1
Northgate Operations7
Northgate Plant1
Northgate Quality18
Riverside Plant6

Footer: 33 records (no truncation notice). Header counts sum to 33, visible data rows 33, and the request the grid issues is:

GET /api/v1/data/duly_task?populate=owner,business_unit&top=100
&select=id,subject,status,due_date,period_key,owner,source,business_unit
&sort=due_date&filter=[["status","in",["open","in_progress"]]]

Counted independently off the seed and matching row for row: 27 open + 6 in_progress = 33, spread 18 / 7 / 6 / 1 / 1 across all five units.

The guard

In test/metadata-bindings.test.ts, alongside#85's grouping-projection guard and in its idiom — not a second mechanism. It pins the decision (by_unit carries the open-work scope) and inventories every grouped grid with the scope it carries, which doubles as the non-vacuity counter.

Scoped to a pin rather than a rule, by measurement: a "grouped grids must be filtered" rule would fire on duly_duty › catalog_tree, which groups two levels deep, carries no filter, and measurably does not need one — the seed holds 31 duties, one page, all groups present. That is the same false-positive trap #85's file narrows away from.

Proven red before the fix, both assertions:

AssertionError: the by-unit lens lost the status filter that keeps its grouping
complete. … expected [] to deeply equal [ 'in_progress', 'open' ]
AssertionError: the set of grouped grids, or the scope one carries, changed …
- "view duly_task › listViews.by_unit · groups by business_unit · status scope: in_progress, open"
+ "view duly_task › listViews.by_unit · groups by business_unit · status scope: (none)"

Nine cases in total, including synthetic self-tests that pin both directions (widened scope, a not_in re-spelling, a filter on another field, and a view that does not group).

What stays broken — please do not read this as a full fix

Even filtered, this lens is structurally page-scoped. A deployment with more open tasks than a page hits exactly this again, with the same silent missing group. The filter buys a correct lens at this product's realistic scale, not a correct mechanism. The durable answer is upstream: objectstack-ai/objectui#7189 (server-side grouping and true per-group counts for a grid; the platform already does this for the dashboard through a dataset, just not on this surface).

So the honest position — and the view's description now says it — is that the dashboard is the authoritative by-unit surface and this lens is for browsing.

One caveat found while verifying, and it matters for that sentence: the description is authored, validated, built and served (GET /api/v1/meta/view/duly_task carries it), but it does not render. The console's ObjectView relay copies label, sort, filter and friends off the active view onto the ListView schema and never copies description, so the branch in ListView that would display it (data-testid="view-description") never receives a value. Nothing errors. Filed as objectstack-ai/objectui#7199, and the view comment says so plainly so nobody believes the caveat is in front of users yet. The key stays authored: it is the spec's home for this sentence, it is served to API/MCP callers today, and it starts rendering the moment the relay is fixed.

Gates

All four green on b780718 (the commit this PR points at):

pnpm validate ✓ Validation passed
pnpm typecheck ✓ (tsc --noEmit, exit 0)
pnpm test ✓ Test Files 22 passed · Tests 591 passed
pnpm build ✓ Build complete — dist/objectstack.json (278.4 KB)

validate prints the one expected hierarchy-security capability warning that AGENTS.md documents as this repo's normal state.

No changeset — this repo has no changeset mechanism; the four gates are the whole contract.


Generated by Claude Code

`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
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. One labelling correction, and the work itself is right.

First, a note on the evidence, because I nearly misread it. The report points at 20-by_unit-verified.png as the after-state. It is not — it shows a single Northgate Operations 33 group full of Done rows, and its mtime falls immediately after before.sh ran. The genuine after-state is 10-by_unit.png, captured three minutes earlier:

Central Office 1 Commissioning file handover — In progress
Northgate Operations 7 Open / In progress
Northgate Plant 1 Open
Northgate Quality 18 Open
Riverside Plant 6 (below the fold)
──
33 footer: "33 records", no truncation notice

Five groups, Central Office present where it was absent before, every row open or in_progress. The two files are named backwards. Worth flagging so nobody re-reviews this later against the wrong image, but it is a filename, not a defect — I confirmed the fix independently against the code and the gates.

The code carries the filter:filter: [{ field: 'status', operator: 'in', value: ['open', 'in_progress'] }], page size untouched, business_unit still in columns from #85.

Gates, re-run by me on the head merged with current main (this branch predates #87, #88 and #90): validate 0, typecheck 0, test 0 — Test Files 24 passed, Tests 615 passedbuild 0.

The narrowing is right, again

The guard is a pin on the by_unit decision plus an inventory of every grouped grid and the scope it carries, not a general "grouped grids must be filtered" rule. Measured reason: that rule would fire on duly_dutycatalog_tree, which groups two levels, carries no filter, and does not need one — 31 duties, one page. This is the third card in a row where the dispatched scope was too wide and the measurement narrowed it correctly. The inventory doubling as the non-vacuity counter is a nice touch: it makes the guard say what it is not covering, so the next reader sees the boundary instead of assuming it.

And the residual is honest

objectui#7199 is the right kind of find: the description explaining that the dashboard is the authoritative by-unit surface is served but never rendered — app-shell's ObjectView relay copies label/sort/filter onto the ListView schema and never copies description. So the caveat I asked to be put on the screen is not on the screen. Saying that plainly in the view comment and not overclaiming it in the PR body is exactly right; the alternative — shipping the note and assuming it lands — would have left us believing users were warned when they were not. The second half of that report (an inline {en, 'zh-CN'} map would render empty even after the relay is fixed) is the kind of detail that saves the platform a second round.

The environment friction you reported at the end — --seed-admin never firing because the app's own seed creates sys_user rows first — is objectstack#14157, and it stopped being your problem about an hour ago: #88 landed, so the demo seed is opt-in and pnpm dev on a clean checkout now mints a working admin. Browser verification should be routine from here.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:57
@os-warren
os-warren merged commit 0557a57 into mainSep 1, 2026
1 check passed
os-warren added a commit that referenced this pull request Sep 1, 2026
A manager opens the schedule to see an overloaded fortnight before it
arrives; nothing about that question involves work that is already done.
On the demo seed 151 of the 186 scheduled tasks are `done`, so the lens
spent nearly all of its height drawing finished bars.
Extends #91's grouped-lens scope inventory rather than adding a second
mechanism: the walk now covers the kanban/gantt/timeline `groupByField`
lenses alongside the grid `grouping` block, names which mechanism each
line is about, and pins the two lenses whose scope is a decision. It is
still a pin-and-inventory, not a rule that every grouped view must be
filtered — that rule fires on `catalog_tree` and on `board`, both fine.
The card's diagnosis was checked in a browser first and does NOT hold:
the gantt is not page-scoped. Its chart is served by the non-grid fetch,
which sends no `top`, so it drew all 186 rows and all 12 owner groups
over the full span, while a separate `top=100` fetch fed the footer
beneath it. No owner group was missing before this change. The view
comment and the guard message say so, so that neither is later cited as
evidence for something that was not measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
os-warren added a commit that referenced this pull request Sep 1, 2026
151 of the 186 scheduled tasks are done, and a gantt of finished work
answers no question this screen is opened with. Filtering to open work also
removes a "more data may be available" footer that was printed under a
complete chart.
The card's diagnosed mechanism is falsified and the correction is recorded
in the view comment and the guard's message: the gantt is NOT page-scoped.
One page load issues two fetches — a paged one feeding only the footer, and
an unbounded one feeding the chart — so all 186 rows and all 12 owner groups
were already drawn. Nothing was being dropped. Owen Pryce's one scheduled
task falls outside the paged fetch, so he is the owner who would have
vanished had the chart consumed it.
#91's scope inventory is extended rather than duplicated.
Upstream: objectstack-ai/objectui#7210.
Closes#96
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The by_unit lens shows 151 finished tasks and pages out a whole business unit — scope it to open work

1 participant

@os-warren