Skip to content

The schedule (gantt) lens is page-scoped at 100 rows, so its timeline range and owner groups are computed over a slice #96

Description

@os-warren

Found on the way through #62's browser pass. Filed unassigned, not claiming, and not fully diagnosed — see "what I did not establish" below.

Observation

The schedule lens footer reads, on the seeded demo:

100 records — Showing first 100 records. More data may be available.

Visible bottom-left in the reproduction screenshot on that card: https://raw.githubusercontent.com/objectstack-ai/duly/b6c452d638777d47a003af42ca53333bdb72d7a6/62-repro-full.png

The store holds 186 tasks (measured in #75's seed and recorded in the by_unit comment in src/views/task.view.ts). The schedule lens filters to rows carrying both visible_from and due_date, so its true result set is some subset of those 186 — and evidently more than the 100 that were fetched.

Why it is worth a card rather than a shrug

The gantt does not just display the fetched rows, it derives from them:

  • the timeline range is min(start) and max(end) over the fetched page, so which span the chart covers is decided by a slice;
  • groupByField: 'owner' buckets over the fetched page, so an owner whose rows all sort past row 100 has no row on the screen and nothing says a person is missing.

That second one is the same shape as #86 on the by_unit grid — the failure there was not a wrong number, it was an absent group with no disclosure — and it is the reason that card scoped the lens to open work rather than raising the page size.

This lens exists so a manager can spot an overloaded fortnight before it arrives. A silently partial one answers that question wrongly in the reassuring direction.

What I did not establish

All three are cheap to settle with the demo running; I stopped because it is outside #62.

Note

This is independent of the two rendering defects on #62 (objectstack-ai/objectui#7203, objectstack-ai/objectui#7204). Those are plugin-gantt drawing the fetched data wrongly; this is about which data is fetched.

Activity

  1. os-warren commented on Sep 1, 2026

    @os-warren
    CollaboratorAuthor

    Triaged — same answer as #86, for the same reason, and the third of your three open questions is the one to settle first.

    You asked whether the remedy is #86's (scope the lens) or an upstream ask alongside objectui#7189. It is #86's, and the argument is the one you already made in passing: a schedule of finished work is not what this screen is for either.

    The schedule lens currently fetches every task carrying visible_from and due_date regardless of status, which on the demo means most of what it draws is months of completed bars. A manager opens this to see an overloaded fortnight coming; nothing about that question involves work that is already done. Filtering to open work makes the lens both correct and the thing it was meant to be — the same test #86 passed: it survives the platform being fixed. If server-side ranging shipped tomorrow we still would not want a gantt of finished tasks.

    That also disposes of your first open question without needing to answer it. Whether pagination.pageSize reaches the gantt adapter stops mattering once the result set fits in a page — and chasing it would be work in service of keeping the wrong rows.

    Scope

    1. Filter schedule to status IN ('open','in_progress'), mirroring what Scope the by-unit lens to open work #91 did to by_unit, and say in the comment that the filter is load-bearing for the timeline range and the owner grouping — not a default worth inheriting. Both derivations run over the fetched page, so widening the lens later silently re-partialises them.
    2. Extend Scope the by-unit lens to open work #91's guard rather than adding a second one. It already inventories every grouped grid with the scope it carries; the natural move is to cover the kanban/gantt groupByField lenses in the same inventory. Follow its idiom — pin the decision and inventory the rest, do not write a rule that every grouped view must be filtered. Fetch the fields the grouped grids group by, and guard it #85 and Scope the by-unit lens to open work #91 both measured that such a rule fires on views that are fine (catalog_tree groups two levels, carries no filter, needs none), and this would be the third time.
    3. Settle your second open question while you are there, because it belongs in the PR either way: with the filter in place, enumerate the owner groups the lens renders and check them against the owners the seed gives open work to. If they match, say so — that is the evidence the fix is complete. If an owner is still missing, that is a different and more serious finding and it should stop this card.

    Do not raise the page size, for the reason #86 gives.

    The residual, which must be stated and not overclaimed

    Like by_unit, this stays structurally page-scoped: a deployment with more open scheduled work than a page hits it again, silently, and the timeline range being derived from a slice means the chart would then cover the wrong span rather than merely omitting rows. objectui#7189 covers the grid half of this; if the gantt's range derivation is a distinct code path from useGroupedData, it is worth a sentence on that issue rather than a new one — check before filing.

    Queueing.


    Generated by Claude Code

  2. self-assigned this
    on Sep 1, 2026
  3. os-warren commented on Sep 1, 2026

    @os-warren
    CollaboratorAuthor

    Claiming this one.

    • Session: session_01SqkTcrxUFci7nqXdbBSe2p
    • Branch: claude/issue-96-schedule-open-work

    Working the PM scope: filter the schedule lens to status IN ('open','in_progress') mirroring #91's by_unit change, extend #91's existing inventory guard to cover the groupByField lenses (pin the decision, inventory the rest — no blanket "every grouped view must be filtered" rule), and settle the card's second open question in the PR by enumerating the rendered owner groups against the owners the seed gives open work to. No page-size raise; residual stated, not overclaimed.


    Generated by Claude Code


    Generated by Claude Code

  4. os-warren commented on Sep 1, 2026

    @os-warren
    CollaboratorAuthor
    {
      "issue": 96,
      "status": "done",
      "branch": "claude/issue-96-schedule-open-work",
      "pr": "https://github.com/objectstack-ai/duly/pull/98",
      "premise_still_valid": false,
      "summary": "Delivered the PM's decided scope in full: the schedule (gantt) lens now filters to status IN ('open','in_progress'), and #91's grouped-lens scope inventory was extended (not duplicated) to read the kanban/gantt/timeline groupByField lenses alongside the grid grouping block — pin the two named decisions, inventory the other two, no blanket 'every grouped view must be filtered' rule. BUT the card's diagnosed MECHANISM is falsified, measured in a browser before any edit: the gantt is NOT page-scoped. One page load issues two fetches of the same set — a top=100 one that feeds only the ListView footer, and an unbounded one (no top; @objectstack/rest applies no cap when limit is absent) that feeds the chart. The chart drew all 186 rows and all 12 owner groups over the true 2026-01-31 -> 2026-12-31 span; 186 task rows + 12 group rows = 198 x 40px = the 7920px the task list scrolls. So the card's second open question settles as NO owner group was being dropped, before or after — the card does not halt. The PM's remedy still stands because its justification never depended on the mechanism ('a schedule of finished work is not what this screen is for'; 151 of 186 scheduled tasks are done), and the filter also removes a 'more data may be available' footer printed under a complete chart. The falsification is written into the view comment and the guard's failure message so neither is later cited as evidence for something not measured. The PM's stated residual ('the gantt derives its timeline range from the fetched rows, so an over-page deployment would draw the wrong span') was restated as measured: that is what WOULD happen if the chart ever consumed the paged fetch, not what happens today. Two judgment calls for PM review: (1) the PR carries 'Fixes #96' — say the word and I will switch it to 'Part of #96' if you would rather the card stay open as a record of the corrected diagnosis; (2) origin/main advanced to #97 mid-task, which edits the same test file, so I merged it in and re-ran all four gates on the merged head.",
      "tests": "All four gates green on the merged head b3ea92f (re-run AFTER the merge commit; sha from that run). Quoting the verdict lines the gates printed, not a bare $? — every run redirected to a file first, exit captured before any pipe. `pnpm validate`: '✓ Validation passed (364ms)' (plus the one hierarchy-security warning AGENTS.md documents as this repo's expected state). `pnpm typecheck` (tsc --noEmit): no diagnostics, exit 0. `pnpm test`: 'Test Files 26 passed (26) / Tests 664 passed (664)'. `pnpm build`: '✓ Build complete (654ms)'; verified the filter reaches dist/objectstack.json at /views/0/listViews/schedule. REVERSE VERIFICATION (predicted direction: red on the new pin + the inventory line; observed exactly that): fix committed FIRST (67bf8bf), then src/views/task.view.ts alone reverted via a script carrying a trap '<restore>' EXIT INT TERM. Mutation confirmed on disk by anchored counts before the run, not by the editor's exit code — open-work filter rules present: 4 (by_unit's survives, schedule's gone) and 'schedule scope block present: 0'; git diff --stat showed 5 insertions / 70 deletions. vitest then reported 'test/metadata-bindings.test.ts (55 tests | 2 failed)' — the schedule pin and the grouped-lens inventory — with 53 passing, and the diff line '- ...status scope: in_progress, open / + ...status scope: (none)'. Restore leg confirmed byte-identical (git diff --quiet HEAD -- src/views/task.view.ts, clean status). No build/dist step is involved: this guard reads authored metadata in-process, so there is no dist staleness leg. BROWSER (the real evidence, as on #62/#91): pnpm demo on my own port 4096, Chromium at /opt/pw-browsers/chromium-1194, killed only the PIDs I started (never pkill -f). BEFORE — footer '100 records / Showing first 100 records. More data may be available.', label 'January 2026', two fetches (100 rows total 186 hasMore true; 186 rows hasMore false), 186 task + 12 group rows, scrollHeight 7920. AFTER — both fetches 33 rows (27 open + 6 in_progress) total 33 hasMore false, footer '33 records' with the warning line gone, 33 + 12 = 45 rows x 40px = scrollHeight 1800, label moved to 'June 2026' (objectui#7203 still wrong, just about a different month — recorded in the comment because the file explicitly warns against narrowing for that reason). OWNER GROUPS, the card's second open question: enumerated by scrolling the virtualised task list end to end and collecting every gantt-task-row-__group__* row; 12 rendered before and 12 after, matching name-for-name the 12 owners the seed gives scheduled open work to (counted server-side over the whole store). None missing. Owen Pryce is the near-miss: his single scheduled task falls outside the 100-row page, so had the chart consumed that fetch he is the owner who would have vanished. Screenshots on throwaway branch claude/issue-96-shots (orphan commit caf71cf, PNGs only) and embedded in the PR — no binaries in the diff.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack-ai/objectui#7210: a gantt lens fetches its rows twice — once paged (feeding a footer that then misdescribes the chart) and once unbounded (no ceiling, unreachable from view metadata); searched first, not a duplicate of #7189/#7203/#7204, and #6419/#6460 on the same ObjectView non-grid fetch are both closed",
        "commented on objectstack-ai/objectui#7189 rather than opening a gantt half of it, per the dispatch: the measurement bounds #7189 to the grid, since the gantt does not share useGroupedData's page-scoping"
      ]
    }

    Generated by Claude Code

  5. os-warren commented on Sep 1, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    (Re-posted: the HTML-comment marker on the previous comment was eaten by GitHub's body sanitizer after it landed, which would have made that copy invisible to the PM's scan. Literal-text marker above instead. This is the authoritative copy; the one above it is identical apart from two angle-bracket fragments the same sanitizer also ate.)

    {
      "issue": 96,
      "status": "done",
      "branch": "claude/issue-96-schedule-open-work",
      "pr": "https://github.com/objectstack-ai/duly/pull/98",
      "premise_still_valid": false,
      "summary": "Delivered the PM's decided scope in full: the schedule (gantt) lens now filters to status IN ('open','in_progress'), and #91's grouped-lens scope inventory was extended (not duplicated) to read the kanban/gantt/timeline groupByField lenses alongside the grid grouping block — pin the two named decisions, inventory the other two, no blanket 'every grouped view must be filtered' rule. BUT the card's diagnosed MECHANISM is falsified, measured in a browser before any edit: the gantt is NOT page-scoped. One page load issues two fetches of the same set — a top=100 one that feeds only the ListView footer, and an unbounded one (no top; @objectstack/rest applies no cap when limit is absent) that feeds the chart. The chart drew all 186 rows and all 12 owner groups over the true 2026-01-31 to 2026-12-31 span; 186 task rows + 12 group rows = 198 x 40px = the 7920px the task list scrolls. So the card's second open question settles as NO owner group was being dropped, before or after — the card does not halt. The PM's remedy still stands because its justification never depended on the mechanism ('a schedule of finished work is not what this screen is for'; 151 of 186 scheduled tasks are done), and the filter also removes a 'more data may be available' footer printed under a complete chart. The falsification is written into the view comment and the guard's failure message so neither is later cited as evidence for something not measured. The PM's stated residual ('the gantt derives its timeline range from the fetched rows, so an over-page deployment would draw the wrong span') was restated as measured: that is what WOULD happen if the chart ever consumed the paged fetch, not what happens today. Two judgment calls for PM review: (1) the PR carries 'Fixes #96' — say the word and I will switch it to 'Part of #96' if you would rather the card stay open as a record of the corrected diagnosis; (2) origin/main advanced to #97 mid-task, which edits the same test file, so I merged it in and re-ran all four gates on the merged head.",
      "tests": "All four gates green on the merged head b3ea92f (re-run AFTER the merge commit; sha from that run). Quoting the verdict lines the gates printed, not a bare $? — every run redirected to a file first, exit captured before any pipe. `pnpm validate`: '✓ Validation passed (364ms)' (plus the one hierarchy-security warning AGENTS.md documents as this repo's expected state). `pnpm typecheck` (tsc --noEmit): no diagnostics, exit 0. `pnpm test`: 'Test Files 26 passed (26) / Tests 664 passed (664)'. `pnpm build`: '✓ Build complete (654ms)'; verified the filter reaches dist/objectstack.json at /views/0/listViews/schedule. REVERSE VERIFICATION (predicted direction: red on the new pin + the inventory line; observed exactly that): fix committed FIRST (67bf8bf), then src/views/task.view.ts alone reverted via a script carrying a restore trap on EXIT INT TERM. Mutation confirmed on disk by anchored counts before the run, not by the editor's exit code — open-work filter rules present: 4 (by_unit's survives, schedule's gone) and 'schedule scope block present: 0'; git diff --stat showed 5 insertions / 70 deletions. vitest then reported 'test/metadata-bindings.test.ts (55 tests | 2 failed)' — the schedule pin and the grouped-lens inventory — with 53 passing, and the diff line '- ...status scope: in_progress, open / + ...status scope: (none)'. Restore leg confirmed byte-identical (git diff --quiet HEAD -- src/views/task.view.ts, clean status). No build/dist step is involved: this guard reads authored metadata in-process, so there is no dist staleness leg. BROWSER (the real evidence, as on #62/#91): pnpm demo on my own port 4096, Chromium at /opt/pw-browsers/chromium-1194, killed only the PIDs I started (never pkill -f). BEFORE — footer '100 records / Showing first 100 records. More data may be available.', label 'January 2026', two fetches (100 rows total 186 hasMore true; 186 rows hasMore false), 186 task + 12 group rows, scrollHeight 7920. AFTER — both fetches 33 rows (27 open + 6 in_progress) total 33 hasMore false, footer '33 records' with the warning line gone, 33 + 12 = 45 rows x 40px = scrollHeight 1800, label moved to 'June 2026' (objectui#7203 still wrong, just about a different month — recorded in the comment because the file explicitly warns against narrowing for that reason). OWNER GROUPS, the card's second open question: enumerated by scrolling the virtualised task list end to end and collecting every gantt-task-row group row; 12 rendered before and 12 after, matching name-for-name the 12 owners the seed gives scheduled open work to (counted server-side over the whole store). None missing. Owen Pryce is the near-miss: his single scheduled task falls outside the 100-row page, so had the chart consumed that fetch he is the owner who would have vanished. Screenshots on throwaway branch claude/issue-96-shots (orphan commit caf71cf, PNGs only) and embedded in the PR — no binaries in the diff.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack-ai/objectui#7210: a gantt lens fetches its rows twice — once paged (feeding a footer that then misdescribes the chart) and once unbounded (no ceiling, unreachable from view metadata); searched first, not a duplicate of #7189/#7203/#7204, and #6419/#6460 on the same ObjectView non-grid fetch are both closed",
        "commented on objectstack-ai/objectui#7189 rather than opening a gantt half of it, per the dispatch: the measurement bounds #7189 to the grid, since the gantt does not share useGroupedData's page-scoping"
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions