Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-gantt): let the gantt block outrank the flat top-level spelling - #6509
Conversation
…pelling `getGanttConfig` checked the flattened spelling first and returned early, so a node carrying both spellings rendered the flat one and every key inside an authored `gantt` block was discarded with no diagnostic at all — not even the `GanttConfigSchema.safeParse` warning, which sat behind that early return. `plugin-map` had the identical two-faces shape ruled the other way (maintainer on objectui#5018, 2026-08-17, landed PR #5156): the block wins, with a dev-mode warning naming the ignored top-level keys. objectui#6469 inherits that ruling. - `getGanttConfig` checks the `gantt` block FIRST and returns it whole; the flat branch is unchanged and is consulted only when no block is present. - `warnOnShadowedFlatGanttKeys` names the shadowed flat keys once per distinct shadowing. It is raised only from the block branch, which neither flattener's output ever enters — `ObjectView` / `ListView` emit the flat keys and no `gantt` key — so it cannot fire on the hot path. - `FLAT_GANTT_CONFIG_KEYS` derives the spec half from `GanttConfigSchema.shape`; objectui's ten `GanttConfig` extensions and the legacy `dependencyField` alias are listed, with a compile-time coverage pin in the new test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
…tt-block-precedence
…the key-coverage guard
- `packages/types/src/objectql.ts` and its zod mirror documented the OLD order
("the flat branch is checked first and RETURNS EARLY", "no equivalent ruling
exists for gantt"). Both now state that the block wins per objectui#6469.
- `KnownGanttConfigKey` strips `GanttConfig`'s inherited index signature. A bare
`keyof GanttConfig` widens to `string`, so the `satisfies` on the extension
list and the coverage pin in the test constrained NOTHING while reading as
guards — measured: the pin came back `string`, not `never`. Same blind
instrument objectui#6051's declaration pin records.
- Adds the changeset, and a pin for the shape published authoring guidance
produces (GanttConfig keys hoisted beside a `gantt` block).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4qCo-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
✅ 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-support-ai
commented
Aug 26, 2026
PM review: ACCEPT at ⛔ The fence held, and I checked it the hard way. The diff touches ⭐⭐ You caught the blind instrument, and you measured it rather than assuming it. The key list cannot go stale. Deriving the spec-modelled half from Dedupe is correct for this component specifically. Producer safety was re-measured, not inherited — and it answers the concern the dispatch raised. Both flatteners spread Clause-② does not apply here, and the empty entry diff is not why. The README carries the flip properly — the precedence paragraph, both inline schema comments, and the knock-on to the Landing: Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6469
getGanttConfigchecked the flattened top-level spelling first andreturnedearly, so a node carrying both spellings rendered the flat one and every key
inside an authored
ganttblock was discarded with no diagnostic at all —not even the
GanttConfigSchema.safeParsewarning, which sat behind that earlyreturn.
plugin-maphad the identical two-faces shape ruled the other way (maintainerruling on #5018, 2026-08-17, landed PR #5156): the block wins, with a dev-mode
warning naming the ignored top-level keys. This PR inherits that ruling per the
triage charter, so the two sibling view plugins now answer the same question the
same way.
What changed
getGanttConfigchecks theganttblock FIRST and returns it whole. Theflat branch is byte-for-byte unchanged and is consulted only when there is no
block — still gated on
startDateField && endDateField, still notsafeParsed (adding validation there is a separate question from precedence).warnOnShadowedFlatGanttKeysnames every shadowed flat key, once perdistinct shadowing, in dev only. Mirrors
plugin-map'swarnOnShadowedFlatMapKeysmechanics.FLAT_GANTT_CONFIG_KEYSderives the spec half fromGanttConfigSchema.shape— the same zod object the block branch validatesagainst — so a key added to the spec reaches the diagnostic without a second
edit. objectui's ten
GanttConfigextensions and the legacydependencyFieldalias are listed, under a compile-time coverage pin.
packages/types/src/objectql.ts, its zod mirror, and the pluginREADME that documented the old order is corrected.
The
ganttblock is not declared. #6475 is untouched — this diff adds noganttkey toObjectGanttSchemaor its mirror.Producer safety — re-measured on today's
main, not inheritedThe card's evidence predated #6472, so it was re-run:
packages/plugin-view/src/ObjectView.tsx:1289-1298—case 'gantt'spreads...(viewOptions.gantt || {})onto the top level.packages/plugin-list/src/ListView.tsx:2220-2235—case 'gantt'spreads...(schema.options?.gantt || {})and...(schema.gantt || {})onto the toplevel.
basePropscarries aganttkey (checked at both definition sites).So both flatteners emit the flat keys and no
ganttkey: their output stilltakes branch 2 exactly as before, and the new warning cannot fire on that path —
which matters more here than it did for the map, because the flat branch is the
hot path for gantt.
git log 9ea4cdee3..origin/mainover both files is empty,so this holds on today's
main.Zero-casualty census — and what it cannot see
Only nodes carrying both spellings change. In-repo:
git ls-files '*.json', walked as an AST): 3 files carry aganttkey — the
examples/schema-catalog/src/schemas/plugin-gantt/*schemas —0 objects carry both spellings.
gantt:block is either a list-view / view-definput that gets flattened downstream (
ListViewtests,P1SpecBridge,app-shell/src/views/ObjectView.tsx), an unrelated namespace (i18n localebundles,
plugin-timeline'sganttdemo variant, the demo'sGANTT_ZH), oran
object-ganttnode carrying the block alone.Casualties in this tree: zero.⚠️ Stated as the limit it is: an in-repo
census measures this tree. Authored metadata living outside it is exactly what
it cannot see — the same limit #6472 stated for #6475.
One in-repo shape does newly warn, and it is a real finding rather than a
casualty: the published
skills/objectui/guides/page-builder.mdhoists sixGanttConfigkeys (quickFilters,autoZoomToFilter,resourceView,assigneeField,effortField,capacity) to the top level beside aganttblock, where nothing reads them. They were already inert before this PR —
that node took the block branch before the flip too — so this PR does not break
them, it stops the silence. Filed as #6508, not fixed here (
skills/**isoutside this PR's file face and carries its own budget).
A gantt-specific divergence from the map case, stated out loud
Reported rather than silently absorbed, because the charter's fork clause asks
for it. It does not change which face wins, so it was not treated as a fork:
ObjectMapConfigSchemais all-optional (8 keys, 0 required). The spec'sGanttConfigSchemarequiresstartDateField/endDateField/titleField(measured: 19 keys, 3 required). So an incomplete block can nowoutrank a complete flat spelling and yield an incomplete config — an outcome
the map flip could not produce.
The block is still taken whole. Gating the flip on block validity, or
merging the flat keys under the block, is precisely the lenient consumer
fallback AGENTS.md #0.1 forbids, and neither is what "what the author wrote
outranks it" means. The author is told twice instead —
Invalid gantt configurationfrom the existingsafeParse, plus the new warning naming theflat keys that lost. Pinned by the "still reports an incomplete winning block"
case.
Verification
Tests are narrowed by path from the repo root, per AGENTS.md, and every heavy
run went through the container's shared verify lock.
Green, on the final commit
9421ba72b:Reverse verification — predicted, then observed
Each leg proved its mutation on disk by counting the target text (not by an
editor's exit code) and restored via
git checkout HEAD -- <absolute path>,re-verified by blob hash and an empty
git diff HEAD. No rebuild isinvolved on either leg: the test imports
./ObjectGanttrelatively, so vitestresolves the mutated source, never a
dist/.Leg A — can the coverage pin fail? Dropped
'lockField'fromGANTT_CONFIG_EXTENSION_KEYS(on-disk proof: 1 → 0 occurrences).It fails, and it names the missing key. Restored (hash matched).
Leg B — the flip itself. Restored the old order by narrowing the block guard
to
schema.gantt && !(schema.startDateField && schema.endDateField)(on-diskproof: original guard 1 → 0, injected guard 1).
Predicted RED, and observed:
5 failed | 5 passed (10).The
'FLAT Alpha'in that first failure is the before-state: the samefixture rendered the flat face's values before this change and renders the
block's after it.
The mixed direction is the point — the five must-not-change pins stayed GREEN
under the same mutation: flat-spelling-only reads, no warning on the flatten
product, no warning for a block with no flat keys, the hoisted-keys case, and
the key-set derivation.
One measurement worth flagging: the guard was blind, and the pin caught it
The first version of the key-coverage pin came back
string, notnever.GanttConfigderives from the spec'sGanttConfigSchemaand inherits anindex signature, so a bare
keyof GanttConfigwidens tostring— and bothsatisfies readonly (keyof GanttConfig)[]and theExclude<…>pin constrainednothing while reading exactly like guards that did.
KnownGanttConfigKeystrips the index signature; the pin then measured 29 real keys and Leg A proves
it can fail. This is the same blind instrument #6051's declaration pin records
("annotate the parameter and see what errors DOES NOT WORK here").
Not measured (stated, not counted as passing)
check:readme-exportsandcheck:doc-snippetsboth exited non-zero asPRECONDITION NOT MET, in their own words — this worktree has only
plugin-gantt's dependency closure built, so the packages they resolve againstare absent.
check:doc-snippetssays it explicitly: "This is 'I could notrun', NOT 'I ran and found errors'". Neither ran against my diff, so neither is
reported as green or red. CI builds the repo and measures both. The README fence
I edited is confirmed intact independently:
check:doc-fencesis green and thefence count is balanced.
Repo-wide
pnpm lintis likewise CI's run. The narrowed lint above is ameasurement, not a skip: the population is the 4 changed source files, the count
comes from eslint's own
--format jsonoutput, and this repo'seslint.config.jsconfigures no type-aware linting (no
parserOptions.project/projectService), so nothing in this diff can move the verdict on a file it didnot touch.
File face
For #6470 / #6471, which are queued behind this on the same hot file:
Inside
ObjectGantt.tsxthe edit is confined togetGanttConfigand the blockinserted immediately above it.
GanttConfigEx,ObjectGanttProps,normalizeTaskType,normalizeDependencies,getDataConfigand the wholecomponent body are untouched — including the identity semantics the sibling
tests rely on (the block path still returns
schema.ganttby reference, theflat path still returns a fresh literal).
Generated by Claude Code
Generated by Claude Code