Skip to content

spec: GanttConfigSchema is strictObject(...).passthrough(), so a mistyped gantt key is silently accepted — the only one of the three view config schemas that does not refuse it #15469

Description

@os-sales

Found while doing #14471 (prose on colorField for timeline / calendar / gantt). Out of that card's scope by its own ruling — #14471 moves nothing in the accept set — so filed rather than fixed there.

The measurement

packages/spec/src/ui/view.zod.ts builds all three view config schemas with the same strictObject(...) helper. Two of them refuse an undeclared key. The gantt does not:

zod 4.4.3
CONTROL inline z.object({...}).strict() rejects unknown = true
CONTROL z.strictObject({...})          rejects unknown = true
Gantt     rejects unknown = false
Calendar  rejects unknown = true
Timeline  rejects unknown = true

Run against the built packages/spec/dist/ui/index.mjs at origin/main ee32e1cb8, parsing each schema's required members plus one key named bogus_key_xyz. Both controls are in the same process, so "the mechanism works in this Zod" and "the sibling schemas in this same file use it" are measured, not assumed.

The cause is declared in the source, not accidental

GanttConfigSchema ends with an explicit, commented .passthrough():

// Forward-compatible: the gantt renderer (objectui plugin-gantt) keeps adding
// config knobs (e.g. lockField / defaultCollapsedDepth) ahead of this schema.
// Passthrough lets those extra fields reach the renderer instead of being
// stripped here, so a renderer release no longer has to wait on a spec release.
}).passthrough());

It is the only .passthrough() in view.zod.ts. So this is a decision somebody made with a reason, and the question here is whether that reason still holds — not whether someone slipped.

Why it is worth a triage decision

  1. strictObject is applied and then undone at that one site. The helper's whole value is its unknown-key error: it names the surface, echoes the offending key and suggests the closest declared key (packages/spec/src/shared/strict-object.ts). .passthrough() disables all of it, so the call reads like strictness to anyone scanning the file.
  2. Same key, same ladder, two behaviours. colorField is declared on all three. An author who writes colourField on a calendar or timeline block gets a named error with a suggestion; on a gantt block they get success and a bar that is not coloured. That is the exact failure shape gantt.colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly #14110 / objectui#7243 just closed one layer down — declaring the key was worse than omitting it — reappearing as a typo the contract will not catch.
  3. The stated rationale is the "second de-facto contract" shape Prime Directive Add comprehensive test suite for Zod schema validation #12 names. "A renderer release no longer has to wait on a spec release" is a forward-compat window, and the 2026-08-27 ruling on staged transitions (创业阶段不渐进) argues against holding one open by default.
  4. It may be invisible to the strictness campaign. The ledger (docs/audits/2026-07-unknown-key-strictness-ledger.md) classifies view.zod.ts and records which sites closed; a grep of the generated counts file for "gantt" returns nothing, so a .passthrough() site does not appear to be counted as strip debt the ratchet can ever retire. Worth confirming either way — if it is genuinely exempt, the exemption should be written down with its rationale where the ledger keeps them.

The keys the window exists for

objectui declares ten GanttConfig members the spec does not model — borderColorField, lockField, objectField, summaryExtent, defaultCollapsedDepth, dependencyTypes, timeZone, exportFileName, interactions, timeSegments (GANTT_CONFIG_EXTENSION_KEYS in packages/plugin-gantt/src/ObjectGantt.tsx at pin 00d3f09c). At least one of them is already advertised to users as shipped: content/docs/releases/v15.mdx:151 names borderColorField. So the choice is real either way — close the window and those ten need declaring, or keep it and the ten stay authorable-but-undeclared.

Decision, not a patch

Both directions are accept-set changes, so this needs triage rather than an implementer:

  • A — declare the ten and drop .passthrough(). One strict contract; typos get the named error; the release note stops advertising a key the spec does not know. Cost: the spec now tracks renderer knobs, and each future one needs a spec release.
  • B — keep the window, write it down. Cheapest today. Cost: gantt stays the one authorable surface where a typo is silent, and the divergence keeps growing.
  • C — declare the ten, keep .passthrough() for the next ones. Fixes the advertised key without closing the window; keeps the silent-typo behaviour.

No recommendation offered here — the four-axis frame belongs to whoever triages this, and 实际业务需求 (who is actually authoring these ten keys, in which app) is the axis I did not measure.

Refs: #14471 (the card this was found under) · #14110 and objectui#7243 (the consumer-side ladder) · Prime Directive #12 · docs/audits/2026-07-unknown-key-strictness-ledger.md

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions