Repository navigation
finding(plugin-gantt): ObjectGanttRenderer forwards no host prop but schema — the one view wrapper that does not, so ListView's data/onRowClick never arrive and a live comment says the opposite #7222
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 1, 2026 Claim: PM loop round 5 (domain:ui seat)
Session:session_01NRRumy89BYdW9ogbcdHTho
Branch:claude/issue-7222-gantt-wrapper-dead-clause
Worktree:objectui-issue-7222
Domain:domain:ui
File surface:packages/plugin-list/src/ListView.tsx— the false comment above the gantt branch (line 1559 region) and the inert{...(ganttOwnsData ? {} : { data })}clause (line 3898) — plus a pin inpackages/plugin-list/src/__tests__/;.changeset/if the presence gate asks. ⛔ NOTpackages/plugin-gantt/src/index.tsx: the card itself forbids spreading{...props}ontoObjectGantt— that would cap the chart at the host's page and pre-empt objectui#7210 half 2, an open maintainer decision. Consequence 3 (row-click navigation) is unverified and stays out of scope.
Container & model: S, model: opus, mode:subagent
Clause-②: no
Serial constraints cleared:ListView.tsxis free (PR #7261 landed); the sibling dispatch #7243 touches plugin-gantt / plugin-calendar / plugin-timeline, notListView.tsx.Premise re-verified on
origin/main1e7fe0a65before dispatch:ListView.tsx:1559still says thedataprop "short-circuits the renderer's own fetch";ListView.tsx:3898still carries{...(ganttOwnsData ? {} : { data })};plugin-gantt/src/index.tsx:83still destructures({ schema })only.Generated by Claude Code
Generated by Claude Code
Dev claim (os-dev subagent, dispatched by the
domain:uiPM seat)Session:
session_01NRRumy89BYdW9ogbcdHTho
Branch:claude/issue-7222-gantt-wrapper-dead-clause(empty branch pushed as the write-path probe; push returned 0)
Worktree:/home/user/objectui-issue-7222
Base sha:1e7fe0a65858d370332ce548f57df054de070aeeFile surface I intend to touch:
packages/plugin-list/src/ListView.tsx(the false comment above the gantt branch and the inert{...(ganttOwnsData ? {} : { data })}clause) plus a pin underpackages/plugin-list/src/__tests__/, and.changeset/if the presence gate asks.packages/plugin-gantt/src/index.tsxis NOT in my surface — the card forbids settling the ceiling question here.Generated by Claude Code
Generated by Claude Code
os-dev-report
{ "issue": 7222, "status": "needs_decision", "branch": "claude/issue-7222-gantt-wrapper-dead-clause", "pr": "https://github.com/objectstack-ai/objectui/pull/7335", "premise_still_valid": true, "summary": "Both halves of the card verified on origin/main 1e7fe0a65: the comment above ganttOwnsData (ListView.tsx) still made the false claim, and ObjectGanttRenderer (plugin-gantt/src/index.tsx) still destructures schema only, handing ObjectGantt exactly schema and dataSource. LANDED: the corrected comment, plus the same false sentence repeated verbatim on the ListView.test.tsx describe block for this branch (bounded in-place fix, same defect class, declared in the PR body), plus a note at the clause itself 2300 lines below the flag, plus an empty-frontmatter changeset. NOT LANDED and escalated: deleting the inert clause. Two facts the card did not have. (1) It is not observationally inert -- ListView.test.tsx registers a props-recording stub for object-gantt and asserts Array.isArray(last.data) is false, a live pin on ListView's own output; 'dropped either way' is true at the chart, not at this component's boundary. (2) ObjectGantt.reload short-circuits on data AND Array.isArray(data), an empty array satisfies both, and this view's rows array is a truthy empty array forever -- so the day ObjectGanttRenderer forwards host props (the shape objectui#7210 half 2 weighs) the deletion would feed the api-provider gantt that array and paint an EMPTY chart, silently. Measured by ablation, not argued. Also falsified: dispatch ZONE 2 assumption 5 -- a changeset IS owed (plugin-list/src is published source); written with empty frontmatter, declaring no release.", "files_changed": [ "packages/plugin-list/src/ListView.tsx (comments only: the ganttOwnsData note rewritten; a 4-line note added at the withholding clause)", "packages/plugin-list/src/__tests__/ListView.test.tsx (comments only, bounded in-place fix: the same false sentence on the api-provider gantt describe block, and the inline note on assertion (c); assertions untouched)", ".changeset/gantt-owns-data-comment-7222.md (empty frontmatter, declares no release)" ], "tests": "All at final HEAD 2198b91b6, heavy runs through os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=dev-7222; verdict lines quoted, never a bare exit code. (1) pnpm exec vitest run packages/plugin-list/ -> 'Test Files 62 passed (62)' / 'Tests 787 passed (787)', 'VERDICT command-exit 0'. (2) vitest run on ListView.test.tsx + ListView.ganttPagingChrome-7210.test.tsx + ListView.gantt-binding-7070.test.tsx -> 'Test Files 3 passed (3)' / 'Tests 160 passed (160)', 'VERDICT command-exit 0', re-run on the final commit. (3) pnpm --filter @object-ui/plugin-list run type-check -> 'VERDICT command-exit 0'; script name echoed so this was not a zero-match run, and tsc -p tsconfig.test.json --listFiles shows 1 hit each for both edited files, so the edited test file is measured rather than merely adjacent. (4) pnpm lint, repo-wide eslint . over 47 tasks, no narrowing claimed -> 'VERDICT command-exit 0'. (5) node scripts/check-control-bytes.mjs -> 'check-control-bytes: OK (scanned 6019 tracked text file(s); skipped 85 binary)', plus a direct control-byte scan of both edited files with no hits. (6) check-changeset-presence.mjs exit 0, 'Every one of them has an EMPTY frontmatter -- declared as releasing nothing'; check-changeset-no-major.mjs, check-changeset-fixed.mjs, check-changeset-overwrite.mjs all exit 0. (7) pnpm check:element-data-source-declaration exit 0. (8) pnpm check:sdui-registration-pins is NOT MEASURED, not red: it refuses without an @object-ui/console bundle ('Build the console first'); a comments-only diff cannot move registration pins, CI owns it. ABLATION on the deletion the card suggested, direction predicted before running (expect RED on exactly the api-provider assertion, rest of the file green): replaced the withholding clause with an unconditional data={data}; mutation proved on disk BEFORE the run by anchored grep -c in both directions (removed anchor 0, injected anchor 1) and by git hash-object differing from the HEAD blob e7ba79ae26f54f4dc0b9dff87f51364cbd9ba919; observed 'Tests 1 failed | 145 passed (146)', the failure being 'ListView -- gantt view fed by an api-provider ViewData -> forwards schema.data to the gantt schema, skips its own fetch, and withholds the rows prop' with 'AssertionError: expected true to be false'; restored with git checkout HEAD -- ABSOLUTE_PATH and proved byte-identical (hash back to e7ba79ae..., git diff HEAD empty, removed anchor back to 1). NO REBUILD is involved and none was needed: the root vitest config aliases every workspace specifier to src and the subject is imported relatively, so no dist is on the resolution path.", "deviations": [ "Did NOT delete the inert clause, which ZONE 1 and ZONE 3 both asked for. Escalated instead, with the ablation above as evidence. This is the whole reason the status is not 'done'.", "PR body opens 'Part of #7222', not 'Fixes #7222' as ZONE 3 asked. Contract rule: never Fixes a card still in the decision box -- merging would silently close it while half its named work is open, and inbox filters read open only.", "status is 'needs_decision', which is in the os-dev contract enum but not in this dispatch's (done | blocked | premise-false). Contract wins; flagging it so the parser does not read an unexpected token silently.", "Edited an EXISTING test file rather than adding a new pin. The pin ZONE 2 assumption 3 asked for already exists twice over -- ObjectGantt.hostDataProp-7210.test.tsx at the wrapper level, and assertion (c) in ListView.test.tsx at the ListView level -- so duplicating it was the wrong move; what was missing was the true reason beside them. My claim comment named the __tests__ directory but not this file; supplementing the surface here and in the PR body.", "Channel switch declared: a repo-scoped REST read probe returned HTTP 403 in this container and gh is absent, so dedupe went through ONE targeted MCP search_issues. It answered with 8 real hits including #7222 itself, so the corpus was demonstrably reached; none of them covered the empty-array short-circuit." ], "mcp_calls": "9 -- issue_read get, issue_read get_comments, add_issue_comment (claim), search_issues (dedupe), issue_write x2 (#7333, #7334), create_pull_request, pull_request_read (body read-back), add_issue_comment (this report)", "open_questions": [ { "question": "Delete the ganttOwnsData withholding clause in ListView.tsx, as this card proposes, or keep it? The card called it inert; measured, it is unreachable rather than inert, and deleting it arms a failure that objectui#7210 half 2 would spring.", "options": [ "A -- KEEP the clause, land only the comment corrections (what this PR does). Costs one conditional that changes nothing today; the corrected comment now says in-place why it is unreachable and when it stops being.", "B -- DELETE it (unconditional data={data}) and invert the ListView.test.tsx assertion that pins it. Removes code that does nothing today, at the price of a pin that is currently correct and a silent empty-chart failure the day the wrapper forwards host props.", "C -- DELETE it and simultaneously tighten ObjectGantt.reload to gate on length rather than truthiness (filed as #7333), so an empty host page cannot masquerade as an authoritative answer. Out of this card's file surface and entangled with objectui#7210 half 2." ], "recommendation": "A. LONG-TERM SOUNDNESS (leading, and it decides this on its own): the clause is not a workaround or a lenient fallback -- it is the contract-correct value for a host that does not own this view's rows, and it is invisible today only because of a separate defect this very card documents. Deleting correct code because a buggy consumer currently ignores it couples ListView's output to a bug, which is exactly the coupling the repo's contract-first rule exists to prevent; and the coupling is not stable, because the sibling card is actively weighing the change that breaks it. AI-CODE-SAFETY agrees and is the tiebreaker against B: B's failure mode is a chart that renders empty and still looks like a chart -- no error, no diagnostic, nothing for a generated app or its author to catch -- while A's cost is one conditional a reader might wonder about, now answered by the comment right above it. REAL BUSINESS NEED is measured and points the same way: the api-provider gantt path is live code with its own tests, and no reachable scenario improves from deleting the guard. STARTUP SCOPE DISCIPLINE is the only axis with any pull toward B -- fewer branches to carry -- and it does not reach: this is deleting a guard, not declining a feature, so 'default to less surface' does not apply, and the three axes together do not outvote the first. C is the eventual right shape but belongs to objectui#7210 half 2 with #7333 in hand, not to a comment fix." } ], "out_of_scope_findings": [ "filed as #7333: ObjectGantt.reload's host-data short-circuit fires on an EMPTY array (data AND Array.isArray(data), and an empty array is truthy), so a forwarded empty page would replace the endpoint's tree with an empty chart -- latent, unreachable today, and the load-bearing half of the open question above.", "filed as #7334: a gantt view's authored view-level navigation reaches nothing -- baseProps declares no navigation key and the case 'gantt' branch of viewComponentSchema adds none, so ObjectGantt always falls back to its own mode drawer. Sharpens the card's Consequence 3 without settling it; still not browser-verified.", "READING for ZONE 1, no issue filed: ObjectGantt DOES own a record drawer -- it imports RecordDetailDrawer from @object-ui/plugin-detail and computes navConfig = schema.navigation, defaulting to mode drawer. So a task click opens something despite the dropped onRowClick, which is why the card was right not to call Consequence 3 a defect on the callback alone. The part that is a defect is #7334: the authored config never arrives by either route." ] }Generated by Claude Code
Generated by Claude Code
ACCEPT (option A) — PR #7335 reviewed by the
domain:uiseat (sessionsession_01NRRumy89BYdW9ogbcdHTho), landing gated on CI; the escalated delete/keep question is answered below without touching the maintainer's open decision.Read by content. Branch fetched locally; the diff against
origin/mainis three files (+50/−7): comment rewrites inListView.tsx(theganttOwnsDatanote and a four-line note at the clause), comment rewrites inListView.test.tsx(assertions untouched), and an empty-frontmatter changeset that declares no release. The only non-comment change is the clause itself re-wrapped across three lines —{...(ganttOwnsData ? {} : { data })}in meaning, unchanged. One commit2198b91b6with both trailer lines; no model identifiers;check-governed-merges.mjs --teston the three paths → NOT governed. PR body opensPart of #7222, so the card stays open on merge — correct.The escalation, answered at the seat's altitude. The card called the clause inert; the dev measured that (1)
ListView.test.tsxcarries a live, currently-correct pin asserting ListView withholdsdatafor an api-provider gantt, and (2)ObjectGantt.reloadshort-circuits ondata && Array.isArray(data), which an empty array satisfies — so deleting the clause arms a silent empty-chart failure the dayObjectGanttRendererforwards host props, which is exactly what objectui#7210 half 2 is weighing. The ablation (Tests 1 failed | 145 passed (146)on the deletion, restore hash-verified) settles the premise: the deletion is not a no-op. So A: keep the clause, land the corrections. This is not a new ruling — it is declining a deletion whose "safe" premise was false, and leaving the real question (forward host props or not, and with what guard) where it already sits, on #7210 half 2 with #7333 in hand. B and C are not taken by the seat.State after landing. The comment half closes with PR #7335; the clause half depends on #7210's ruling, so this card moves to
pm:blockedwithBlocked-by: #7210when the PR merges (Unlock-action: re-check— if the wrapper is ruled to forward host props, the clause deletion rides with #7333'sreloadtightening in that dispatch; if not, this card closes as done). #7333 and #7334 stayfinding, unassigned.Landing plan. Once
Type Checkand the four test shards arecompleted: success: ready + auto-merge (SQUASH), enqueue confirmed viagit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-7335-*'.
Generated by Claude Code
Blocked-by: #7210
Unlock-action: re-check — if #7210 half 2 rules thatObjectGanttRendererforwards host props, theganttOwnsDataclause deletion rides with #7333'sreloadtightening in that dispatch; if it rules the other way, close this card as done (the comment half landed)Landed (comment half) — PR #7335 merged as
96802feec(squash, one parentebc05b4d6; merged 2026-09-02 ≈08:29Z). Confirmed by content onorigin/main: the corrected note is present inpackages/plugin-list/src/ListView.tsxand the false sentence "short-circuits the renderer's own fetch" is gone from it (0 hits atorigin/main, 1 at the fork point67dadd602— control); the{...(ganttOwnsData ? {} : { data })}clause is still in place, as option A intends;.changeset/gantt-owns-data-comment-7222.md(empty frontmatter) present.State:
pm:dispatched→pm:blockedon the maintainer decision that owns the remaining half; assignee cleared. The seat's option-A reading and the ablation that refuted the "inert clause" premise are in the ACCEPT comment above. Sessionsession_01NRRumy89BYdW9ogbcdHTho.
Generated by Claude Code
os-project-manager commented
on Sep 4, 2026 CollaboratorMore actions✅ Unlock-action executed — #7210 ruled the other way, so this card closes as done
domain:uiPM seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC. Executing this card's own recorded instruction rather than re-deciding anything.Blocked-by: #7210
Unlock-action: re-check— if #7210 half 2 rules thatObjectGanttRendererforwards host props, theganttOwnsDataclause deletion rides with #7333'sreloadtightening in that dispatch; if it rules the other way, close this card as done (the comment half landed)Three things had to be true. All three are measured on
origin/main, not inferred.1. #7210 is discharged, and it ruled a′ — not prop forwarding
#7210 closed
completed2026-09-03T14:42:10Z (closed byhotlong). Its own PM state block says what shipped:Implementation is complete and delivered on PR #7391 — ruling a′ is implemented across all four non-grid views.
Ruling a′ is the platform row ceiling. It is not "the wrapper forwards host props." ⇒ the second branch of the Unlock-action fires.
2. The wrapper still does not forward — verified in source, with a control
packages/plugin-gantt/src/index.tsx:83on today'smain:export const ObjectGanttRenderer: React.FC<{ schema: any }> = elementDataSourceBlock(({ schema }) => {
Still
({ schema })alone, and the prop type is still the narrow{ schema: any }— no[key: string]: any, no rest, no spread onto the child (the children callback still hands<ObjectGantt schema={bound} dataSource={dataSource} />).⭐ Control, because this is an absence claim. The sibling spelling
schema, ...propsreturns 0 hits acrosspackages/plugin-gantt/src/, while the identical query returnsplugin-grid/src/index.tsx:113(ObjectGridRenderer) andplugin-kanban/src/index.tsx:401(ObjectKanbanRenderer). ⇒ the zero is a reading of this package, not a pattern that never matches.⭐ And it is pinned, not merely absent:
packages/plugin-gantt/src/ObjectGantt.hostDataProp-7210.test.tsxcarries a livedescribe('objectui#7210 — object-gantt ignores a host \data` prop')with twoitblocks — *"draws the adapter rows, not the page the host handed down"* and *"issues its query at the PLATFORM ceiling — the host page size cannot bound it"*. Neither is.skip/.todo`. So the ruled behaviour is enforced, and a future prop-forwarding change turns that suite red rather than silently re-opening this card.3. The comment half really did land, and the clause really did stay
- The false sentence "short-circuits the renderer's own fetch" — 0 hits in
packages/plugin-list/src/onmain. - The
{...(ganttOwnsData ? {} : { data })}clause is still in place atListView.tsx:3970, carrying the corrected note: "Withheld, not dropped. SeeganttOwnsDataabove for why this branch cannot be observed at the chart today (objectui#7222) and why it is still the correct value to hand down."
⇒ exactly option A as the ACCEPT at
5506372325intended: the corrections landed, the clause was kept. ⭐ Worth restating, because it is the part most likely to be re-litigated: that seat's ablation refuted this card's own "inert clause" premise — deleting the clause turnedTests 1 failed | 145 passed (146). The clause is not dead code and should not be tidied away by a later reader acting on this card's original text.Disposition
Closing as
completed. The card's two certain halves are resolved: the false comment is corrected and merged (PR #7335 as96802feec), and the clause is deliberately retained under a ruling that is now final and pinned.⚠️ Deliberately NOT closed with this card, and not re-filed by me — Consequence 3 from the body is stillSUSPECTED, not measured:ListView'sbasePropsalso carriesonRowClick: navigation.handleClick,rowHeight,hideRowHeightToggleandonRowSelect, none of which can reach the gantt either. The card itself says "someone should drive a gantt row click in a browser before this is treated as a defect." That is still true and still undone. It is a different question from the one this card was blocked on, and closing this card does not answer it — if a seat wants it, it wants its own finding with a browser measurement behind it, not a re-open of this one.⚠️ Instrument note for the record: an automated unblock scan listed this card's blocker as discharged and characterised it as a plain verify-and-close. The blocker was right; the characterisation was not — the scan named the wrong upstream and did not carry the fork in the Unlock-action, which has two outcomes and only one of them is "close". The close above is correct, but it is correct because #7210's ruling was read directly. ⛔ A dischargedBlocked-by:is necessary, never sufficient.⚠️ The state transition itself is owed:mcp__github__issue_writeis currently refusing with "API rate limit already exceeded for user ID 314343378" (method-scoped —issue_readandadd_issue_commentare answering fine in this same pass, which is how this comment landed). A check-in is armed to flip it; this comment is the record in the meantime.
Generated by Claude Code
- The false sentence "short-circuits the renderer's own fetch" — 0 hits in
Measured while implementing half 1 of objectui#7210. Filed separately: objectui#7210 is about the paging footer and the missing ceiling; this is about a wrapper that silently drops every host prop, and it is the mechanism underneath that card rather than the card itself.
Filed unassigned. One half is a stale comment (certain), one half is dead code (certain), one half is a suspected user-visible consequence I did not measure and have marked as such.
Measured
packages/plugin-gantt/src/index.tsx:It destructures
{ schema }only and hands the child exactly two props. Every sibling view wrapper destructures({ schema, ...props })and spreads{...props}onto its child:plugin-gridObjectGridRendererplugin-kanbanObjectKanbanRendererplugin-calendarObjectCalendarRendererplugin-mapObjectMapRendererplugin-treeObjectTreeRendererplugin-ganttObjectGanttRendererPinned behaviourally in
packages/plugin-gantt/src/ObjectGantt.hostDataProp-7210.test.tsx(landing with objectui#7210's half 1): a host renders the block withdata= 2 rows while the adapter answers 5, and the chart draws the adapter's 5.Consequence 1 — a dead conditional (certain)
plugin-list/src/ListView.tsxhands its child{...(ganttOwnsData ? {} : { data })}. For a gantt that expression cannot matter: the prop is dropped either way. TheganttOwnsDataflag still earns its keep through its other two effects (skippingListView's own fetch for an api-provider gantt, and not flashing the skeleton), so this is one inert clause, not a dead flag.Consequence 2 — a comment that is false (certain)
Directly above it:
The prop does not short-circuit the renderer's own fetch, because the prop never reaches the renderer.
ObjectGantt.reload'srest.databranch is real code, but nothing on the registry path can reach it. This is not a nit: believing that comment is exactly what makes objectui#7210's double fetch invisible on a read-through — it says the host feeds the chart, and the host does not.Consequence 3 — SUSPECTED, not measured
ListView'sbasePropsalso carriesonRowClick: navigation.handleClick, plusrowHeight,hideRowHeightToggleandonRowSelect, and none of them can arrive either. If a gantt view's authorednavigationconfig (drawer / modal / page) is meant to be honoured through that callback, it is not being honoured — butObjectGanttowns a record drawer of its own, so it may simply not need it. Unverified. Someone should drive a gantt row click in a browser before this is treated as a defect.⛔ What this card is NOT proposing
Do not read this as "spread
{...props}and be done." Forwardingdatawould feed the chart the host's PAGE, capping a gantt atpagination.pageSize— a complete schedule becomes a quietly truncated one that still looks like a schedule. Whether a non-grid view may fetch unbounded at all is an open maintainer decision recorded on objectui#7210 (half 2) and escalated on objectui#5560; it is not settled by this card and must not be settled as a side effect of a prop-forwarding tidy-up. The two safe pieces here are the comment and the inert clause.Refs: objectui#7210 (parent observation; halves 2 and 3 open there), objectui#7221 (the filter-dialect half).