Repository navigation
fix(plugin-gantt): an EMPTY host data array is "no host rows yet", not the rows (objectui#7333) - #10812
Conversation
…not the rows `ObjectGantt` read a host `data` prop at two sites and both accepted `[]`: `reload`'s short-circuit (`data && Array.isArray(data)`; `[]` is truthy) adopted it and returned before the chart's own query, and `recordQueryDerivesExpand` (`Array.isArray(data)`) switched off the object-schema gate and the invalidation subscription of that query. A gantt whose `data` names its own source painted an EMPTY chart whenever a host handed it an array it had not filled yet. Both sites now read one predicate, `hostRows`: a host array is adopted only when it has rows. `reload` reads it through `hostRowsRef`, so host rows that arrive after an empty first render are the rows the re-fired fetch effect adopts, not the `[]` its closure saw. The two plugin-list comments that described the old short-circuit as the reason `ListView` withholds its rows are corrected to the new behaviour (comment-only). Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: 117/117 Read: card objectui#7333 (body and all five comments: routing 5548457269, stale-block pointer 5853812598, direction 5857177033, seat claim 5857391261, and the os-dev-report 5857798620 for its empty ① Derived judgmentsJ1. The J2. J3. J4. The measured edge (host J5. J6. The changeset, sentence by sentence against the head — all TRUE. "no longer treats an EMPTY host J7. The edited comments, sentence by sentence — all TRUE on the head. J8. Pending changesets naming these files — none made false. J9. Tests and discipline. The five cases pin the two sites and the arrival; cases 2 and 3 are real controls (endpoint never read for host rows; endpoint read with no prop). The negative assertion in case 5 waits a 100 ms ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #7333
Clause-②: no (copied from the claim): the gantt stops treating an empty host array as authoritative. It keeps fetching from the view's own source, as it does with no host array at all; no declared key, export or accept set moves.
What changed
ObjectGanttread a hostdataprop at two sites, and both accepted[]:reloadtesteddata && Array.isArray(data).[]is truthy, so the chart adopted it and returned before its own query;hasHostData, a bareArray.isArray(data)) fedrecordQueryDerivesExpand, so the same[]also switched off the object-schema gate and the data-invalidation subscription of the query the chart should still issue.Both sites now read ONE predicate,
hostRows, evaluated once per render: a host array counts only when it has rows. An empty one reads as "no host rows yet", and the chart reads from its own source exactly as it does with no host array at all. A non-empty host array is adopted as before.reloadreads the predicate throughhostRowsRefinstead of closing overrest.reloaddoes not key on the host rows, so a closed-over value is the one from whichever render last rebuiltreload. When a host's rows arrive after an empty first render,recordQueryDerivesExpandflips and re-fires the fetch effect, and that run has to see the rows that flipped it. Ablation A3 below shows that the closure form gets this wrong. Theeslint-disablereason onreload's dependency list said(rest as any).data intentionally untracked, which is no longer true. It now names what it actually suppresses,searchFields, measured by linting the file with the directive stripped: base reportsrestandsearchFields, head reportssearchFieldsonly.Which gate, and why (objectui#7210 half 2, ruling a′, PR #7391)
The card left this open: gate on length, or on an explicit "the host owns the rows" signal. Its routing note said the length gate is enough if the wrapper does not forward host props. Ruling a′ lets a non-grid view fetch the whole filtered set up to a platform ceiling, and PR #7391 implemented it with the
object-ganttrenderer still forwarding nothing. Its "Not in this PR" section calls forwardingdata"the explicitly fenced-off wrong fix". So no host owns a gantt's rows on the registry path, and none is planned. Adding a host-ownership signal would be a new prop contract with nothing pulling for it. The length gate is therefore the one that matches the ruling.Premise and reach (measured on
6cf599985)Tests 3 failed | 2 passed (5). Only the two controls pass.ObjectGanttadataprop today.ObjectGanttRenderer(index.tsx) still destructures onlyschemaand rendersObjectGanttwithschemaanddataSource, andObjectGantt.hostDataProp-7210.test.tsxpins that. A tree-widegit grepfor direct JSX uses ofObjectGanttoutside tests turns upindex.tsxanddemo/main.tsx, both passing onlyschemaanddataSource, plus the README and docs examples, which passschema,dataSourceandonTaskClick.ObjectGanttPropsdoes not declaredata, so a typed caller can only reach this through a cast or an untyped spread, which is how the existing tests do it. ⇒ This hardens a published component's input. It does not fix a live defect. The triage ruled it owed anyway.ListView's rows array for an api-provider gantt view:useStatestarts it as[], and the fetch effect'sganttOwnsDatabranch returns without writing it. So from mount it is exactly the[]this card is about.ListViewwithholds it from the chart, which is unchanged here.Surface note: two plugin-list comments corrected (outside the claimed file surface)
My change made two comments false, so I corrected them. Both are comment-only, with no code bytes changed:
packages/plugin-list/src/ListView.tsx, theganttOwnsDatablock, said the chart short-circuits on[]and that forwarding would paint an EMPTY chart;packages/plugin-list/src/__tests__/ListView.test.tsx, the comment on the api-provider ganttdescribe, said the same.Both now say that an empty host array no longer blanks the chart and that the withholding still guards against a non-empty array being adopted in place of the endpoint's rows.
ObjectGanttRendererand thepersistLayoutKeycomment are untouched, as the claim requires.Tests
New file
packages/plugin-gantt/src/ObjectGantt.emptyHostData-7333.test.tsx, five cases:data: []: the endpoint is read and its rows paint;data: the endpoint is read;data: []: ONE query, issued after the schema settles, already carrying$expand(this pins the gating-flag site).Ablation, run once with a prediction written before each run. Mutations go through
ablation-replaceWRAP mode: the anchor hit once, the blob moved, the restore was proven against the HEAD blob, andgit diff HEADwas empty after each leg. Tests import./ObjectGanttrelatively, so the resolution path issrcand no build is involved.Tests 3 failed | 2 passed (5)Array.isArrayTests 2 failed | 3 passed (5)hostRows, not the refTests 1 failed | 4 passed (5)Local gates, all at HEAD
404ea62vitest run --maxWorkers=2 packages/plugin-gantt/ packages/plugin-list/src/__tests__/ListView.test.tsx:Test Files 83 passed (83),Tests 764 passed (764), lockVERDICT command-exit 0. That is 82 plugin-gantt test files plusListView.test.tsx.pnpm --filter @object-ui/plugin-gantt type-check(tsc --noEmit && tsc -p tsconfig.test.json):TYPECHECK_EXIT=0, after building the@object-ui/plugin-gantt^...closure (Tasks: 12 successful, 12 total).--listFilesOnlyon the test program shows the new test file and 82 test files in it.check-changeset-presence:✅ 4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s).check-changeset-fixedandcheck-changeset-no-major: ✅.check:changeset-claims(report-only): 9 pending changesets nameObjectGantt.tsxorListView.tsx. I read each paragraph, and none describes the host-data short-circuit.check:pending-changeset-literals,check:control-bytes,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:unreferenced-sources: all OK.check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).node scripts/check-governed-queue-guard.mjs --teston the 5 paths:✅ NOT GOVERNED.pnpm lintis each package'seslint ., and the rootlint:rootignorespackages/*. So the changed files are linted by the plugin-gantt and plugin-list configs.eslint --format jsonreports 2 + 2 files, 0 errors, 0 ignored.eslint.config.jssets noparserOptions.project/projectService, so type-aware linting is off, and no local rule reads another file. So this diff cannot move a verdict on an untouched file.ObjectGantt.tsxwarnings went from 84 at base to 81 at head:no-explicit-any75 to 71, andreact-hooks/refs2 to 3 (the render-time ref write, the same pattern aseffectiveDataSourceRefandreloadRef).check:*families are left to CI.Acceptance notes (not filed)
data: []into a gantt with no record source and nodataSourcenow sees the chart's own error, "DataSource required for object/api providers", where base drew an empty chart. I measured this with a throwaway probe against both blobs and deleted the probe afterwards. It follows directly from the ruled semantics: the chart reads its own source, and here it has none. Once the host's rows arrive, the host branch adopts them and clears the error, as the adapter branch does. That last sentence is read from the code; I did not measure it for this no-source case.ListView.tsx'sganttOwnsDatablock still calls objectui#7210 half 2 "an open maintainer decision", and thesurfaceDrawsFetchedRowsblock still says the gantt query "carries no$topat all". Both have been stale since PR feat(non-grid): a platform row ceiling with a loud footnote, and the settled-schema convergence #7391. I left them alone because they are outside this card. Carrier: none.Session:
https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY(PM seatdomain:ui#4, dispatched os-dev run).Generated by Claude Code