Repository navigation
fix(plugin-list,plugin-view): omit gantt progress/dependencies rather than inventing them (#7499) - #8876
Merged
Conversation
… than inventing them (objectui#7499) Flavour 3 of objectui#7070 — the half its 2026-09-01 ruling ordered carded separately, explicitly forbidding the date-axis conclusion from being imported. Two faces floored `progressField` at `'progress'` and `dependenciesField` at `'dependencies'` for every gantt view, declared or not. app-shell carries neither key (measured; its `titleField` floor is the control proving that zero is not a dead probe). The remedy is OMIT, not refuse. A fabricated date axis has no legitimate twin — every bar lands on a column nobody declared — whereas a fabricated progress/dependencies name yields a per-row `undefined`, indistinguishable from the legitimate and common case: most gantt rows have neither. Refusing would break that common case; fabricating manufactured a binding the author never wrote, and its failure was silent. Declared values still pass verbatim through the existing `gantt` / `options.gantt` spreads, which always had the last word over these two lines — the floor was all they ever contributed. The two pins #7070 left as a declaration slot are flipped here, with the reason on the cases. `ObjectGantt.unconfiguredRefusal-7070` is the non-regression control and stays green: `getGanttConfig` gates on the two date fields alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Contributor
✅ 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
|
os-zhuang
marked this pull request as ready for review
September 9, 2026 18:36
This was referenced Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7499
Flavour 3 of #7070 — the half its 2026-09-01 maintainer ruling ordered carded separately and judged on its own terms, ⛔ explicitly forbidding the date-axis conclusion from being imported. The ruling did not decide this card's substance; it decided who decides, and how not to. The substantive call recorded here came from the dispatching PM seat, not the maintainer.
The ruling implemented: OMIT the key when it was not declared
Two faces floored the pair for every gantt view, declared or not:
packages/plugin-list/src/ListView.tsxprogressField … || 'progress',dependenciesField … || 'dependencies'packages/plugin-view/src/ObjectView.tsxThe app-shell face carries neither, verified here rather than inherited:
grep -c progressFieldonpackages/app-shell/src/views/ObjectView.tsxreturns 1, and that hit is a string in the dev-modeflatKeysdiagnostic array — ⛔ not a floor, untouched.|| 'progress'in that file counts 0. Control that the zero is not a dead probe: app-shell does floortitleFieldat'name'for gantt. Second control:|| 'start_date'/|| 'end_date'/|| 'created_at'count 0 across all three files — the date axes really are retired.The asymmetry that decides it, and the reason the date-axis conclusion must not be imported:
progress/dependenciesname yields a per-rowundefined, which is indistinguishable from the legitimate and common case: most gantt rows have neither.⇒ Refusing would break that common case. Fabricating manufactured a binding the author never wrote, whose failure was silent — an author who spelled the key differently got an accidental hit on a same-named column and no diagnostic. Omission does neither.
Mechanically this is a deletion: both call sites already ended with
...(schema.options?.gantt || {})/...(schema.gantt || {})(resp....(viewOptions.gantt || {})), which always had the last word over these two lines. The floor was the only thing they ever contributed, so a declared value still arrives verbatim through either authoring nesting.⭐ The falsifiable premise, measured FIRST
The only population whose behaviour actually changes: a schema carrying a column literally named
progressordependenciesthat does NOT declare the corresponding field key. Those work today by accident and stop after omission.Result: empty. Measured before anything was deleted.
Sharp predicate — a gantt view routed through one of the two floored faces (a
list-viewwithviewType: 'gantt', or a plugin-viewObjectViewgantt entry), on data carrying the literal column, without the key declared:Both are individually judged not real authored views: they carry the literal string only as the assertion of the floor itself, and they are the two pins this card is chartered to flip. Everything else was excluded on stated grounds, not by hand-waving:
examples/schema-catalog/src/schemas/plugin-gantt/*.json— two of the three DO carry a literaldependenciescolumn, and both are out of population on two independent counts: they declaredependenciesField: "dependencies"explicitly, and they are directtype: "object-gantt"schemas, which takegetGanttConfig's first branch, where these floors never applied.content/docs/plugins/plugin-gantt.mdx,packages/plugin-gantt/README.md,skills/objectui/guides/page-builder.md— directobject-gantt/ganttrenderer usage ('gantt'is registered straight toObjectGanttRenderer), and every example carrying the literal column declares the key beside it.progress(plugin-detailpercentClip test,plugin-formderiveMasterDetail test,plugin-gridbulk-actions demo) mention gantt zero times.progress/dependencieskey" is the two example JSONs above.Positive control that the census can speak. Relaxing exactly one conjunct — dropping "does not declare the key" — turns the same pipeline from 0 sites to 51, and the sharp predicate itself returns 2 rather than 0. A pipeline that returns nothing under every input would have returned nothing here too.
.png, 1.xlsx)packages/plugin-grid/demo/sample.xlsx, a spreadsheet-import demo fixture. None is a view-metadata carrier.schema.gantt/viewOptions.gantt, which in production come from the server's object and listview metadata. The census covers the repo corpus the card named; it cannot see a customer's schema.Tests
Everything below ran on the tree that is commit
544e95db2.ListView.gantt-binding-7070andObjectView.ganttBinding-7070, plus the non-regression controlplugin-gantt/src/ObjectGantt.unconfiguredRefusal-7070(⛔ untouched): 3 files, 25 tests passed. The reason for omit-rather-than-refuse is written into the case names and docblocks, which is what finding(views): the gantt and timeline branches still fabricate date-axis field names on all three faces — and app-shell's own #3129 note certifies them as already fixed #7070 left the slot for.packages/plugin-list+packages/plugin-view: 109 files, 1206 tests passed.plugin-view/objectViewHostSurface,core/column-identity.ratchet,types/tree-view-config-readers-8253,types/object-kanban-group-by-limit-7322,types/object-view-unmirrored-keys-7779,types/object-kanban-record-source-7780,types/schema-reference-named-list-view-keys-7923,types/zod-mirror-parity,types/gantt-flat-config-declared-keys,types/gantt-dependency-field-deprecated-alias,plugin-grid/gridNonAuthorKeys,plugin-list/forwardref-props-annotation.guard,app-shell/defaults-maps-mirror-en-pack,app-shell/print-stylesheet-4462,app-shell/ObjectView.ganttBinding-7070, and all ofpackages/plugin-gantt: 77 files / 819 tests, plus a post-commit re-run of the ratchet family and the source-text pins at544e95db2: 6 files / 162 tests. All green.@object-ui/plugin-listand@object-ui/plugin-view, bothtsc --noEmitlegs includingtsconfig.test.json: green.TS2307 Cannot find module '@object-ui/components'— that was NOT MEASURED, not a failure: the dependency closure was unbuilt. Green only afterturbo run build --filter '@object-ui/plugin-list^...' --filter '@object-ui/plugin-view^...'.eslint . --no-inline-config --format jsonread 4668 files (the count is eslint's own, from its JSON output). All four changed files report 0 errors. The CI-shapedturbo run lintfor both packages exits 0. The 94 errors that run reports elsewhere are pre-existing, in files this diff does not touch, and are an artefact of--no-inline-configstripping inline disables — not this repo'slintscript.package.jsonand.github/workflows/(objectui has noscripts/pm/dispatch-gates.mjs). Green:check:comment-mask-corpus,check:control-bytes,check:doc-examples,check:doc-snippets,check:doc-types,check:dist-completeness,check:readme-exports,check:handler-key-reads,check:icon-record-names,check:docs-route-closure,check:spec-symbols,check:unreferenced-sources,check:element-data-source-declaration,check:doc-example-readers,check:vi-mock-specifiers,check:vi-mock-inherit,check:shell-escape-residue,check:lint-rule-coverage,check:i18n-keys,check:changeset-presence,check:governed-queue-guard --test(NOT GOVERNED, 5 paths).check:doc-examplesmatters most here — it carries literal line anchors into both edited files (ListView.tsx:808,ObjectView.tsx:618/632/649), all above the deletions; it prints its own verdict, "Every covered@examplecompiles, or fails exactly as its ledger row declares."check:eager-closureandcheck:sdui-registration-pins. Both exit 2 withPREREQUISITE NOT METwording of their own — "This is exit 2, not a pass", "a broken gauge, not a passing budget" — because they weigh anapps/consolebundle that was not built. Reading either as green would be reading a non-measurement as a pass. Neither can move on this diff (no import graph or registration changed), but that is an argument, not a measurement.Ablation — from the committed tree, the floors put back
Direction predicted before running: the OMITS cases go RED, every declared-value CONTROL stays GREEN, and the refusal-screen control stays GREEN in both worlds.
On-disk proof, taken BEFORE the run — marker counts moved and both blobs diverged from HEAD:
RED, by test-case name — 4 failed, 21 passed:
OMITS \dependenciesField` rather than inventing it, when only the OTHER gantt keys are declared (objectui#7499)`OMITS both non-axis keys for a gantt view that declares no config (objectui#7499)OMITS \progress` / `dependencies` rather than inventing them — the retirement finding(views): the gantt and timeline branches still fabricate date-axis field names on all three faces — and app-shell's own #3129 note certifies them as already fixed #7070 left a slot for (objectui#7499)`CONTROL: declaring ONE of the pair leaves the other absent, not fabricated (objectui#7499)—GREEN in both worlds — the real controls:
CONTROL: a DECLARED \dependenciesField` still passes verbatim,CONTROL: the legacy `options.gantt` nesting carries the pair verbatim too,CONTROL: a DECLARED `progressField` / `dependenciesField` still passes verbatim, every #7070 date-axis case, and all **5** cases ofObjectGantt.unconfiguredRefusal-7070— including "refuses even when the out-of-scopeprogress/dependenciesfloors are still handed in".getGanttConfig` gates on the two date fields alone, so removing this pair cannot resurrect a config; the refusal screen is exactly as reachable as before.Restore proven BY STATE, not by exit code:
git diff HEADempty,git status --porcelainempty, and both on-disk blobs back to their HEAD hashes (aae0b1da2…,c1a807af6…). The mutation script restored through atrap … EXIT INT TERMusing absolute paths andgit checkout HEAD -- …— never a baregit checkout --, which reads the index. Re-ran the three files from the restored tree: 25/25 green.Clause-② —
no, measured, with a control that speaksRemoving a fabricated default is a narrowing: no new exported symbol, no new key on a published payload.
packages/types/src/zod/objectql.zod.ts— a file the tool's declared objectui mirror row covers — to a copy of this very diff and re-running the identical command still exits 0. The CLI has no--repoflag andmain()never passes one, so it always judges as objectstack, where that row is inert.The measurement that does speak, via the module's own exported judge:
⇒ The control fires, this diff is clean.
Clause-②: no. Noneeds:contract-reviewlabel, so nothing parks.The CLI gap is filed as objectstack#17217, a sub-issue of the open objectstack#17112, which edits the same output line. ⛔ The enqueue gate is not affected —
check-clause2-carriers.mjsthreads the repo correctly viaPM_SWEEP_REPO/GITHUB_REPOSITORY; the trap is only the hand-run CLI.验收备注
What this omission means for
dependencyField(#6470) — ⛔ not woken, but it moves.getGanttConfig's flat branch readsschema.dependenciesField || schema.dependencyField. While the floor stood,dependenciesFieldwas always truthy through these two faces, so the declared legacy singular alias was unreachable via them — shadowed, never read, for as long as the floor existed. With the floor gone the alias limb becomes live: a view that wrotedependencyFieldnow resolves through it, which is what its@deprecatedcontract already promises and whatObjectGantt.dependencyAliasalready pins at the renderer. Measured: zero sites at either routed face declaredependencyField, so no in-repo view moves. Whether the alias is retired or sanctioned remains #6470's question, deliberately untouched here — but a future retirement should know that this PR is what made the alias reachable through the view layer at all.Out-of-scope findings.
check-widening-tells.mjs's CLI cannot be told the repo, so its declared objectui mirror surface is unreachable and every objectui diff reads clean. Reproduction and controls in the card.CONTROL: declaring ONE of the pair leaves the other absentis named as a control but asserts an omission, so it fails under the mutation like a measurement. Cosmetic naming only; the case is correct and the file already carries three true two-world controls. Taker: whoever next editsObjectView.ganttBinding-7070.test.tsx.packages/plugin-list/src/ListView.tsxandpackages/plugin-view/src/ObjectView.tsxboth computestartDateField/endDateFieldinto a conditional spread that the trailing...(schema.gantt || {})would deliver anyway — the same redundancy this PR removed for the non-axis pair, left in place because the date axes are ⛔ out of scope for this card. Not a defect; the explicit form is arguably clearer. Taker: whoever next revisits the finding(views): the gantt and timeline branches still fabricate date-axis field names on all three faces — and app-shell's own #3129 note certifies them as already fixed #7070 date-axis shape.Risks
Low, and the shape of the risk is stated rather than assumed. The only behaviour that moves is the accidental-hit population, measured empty in this repo and unbounded outside it. A deployment carrying a
progresscolumn and a gantt view that never declaredprogressFieldloses its progress fill — silently, exactly as silently as it gained it. If that turns out to matter for a real tenant, the alternative the census was there to trigger — keep the floor and document it, or omit plus a migration note — is a maintainer call, not this PR's.Rollback: revert the commit. Both faces are pure deletions inside one
case 'gantt'branch; nothing else in either file was touched, and no other package depends on the removed keys being present.🤖 Generated with Claude Code
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Generated by Claude Code