Uh oh!
There was an error while loading. Please reload this page.
fix(types): widen seven zod mirrors the renderer already implements (#5927 group A) - #6032
Merged
yinlianghui merged 3 commits intoAug 24, 2026
Merged
Conversation
…d-validator change Refs #5927
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
|
This was referenced Aug 24, 2026
yinlianghui
marked this pull request as ready for review
August 24, 2026 14:08
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 24, 2026
Merged
This was referenced Aug 24, 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 freeto 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.
Refs #5927 — group A only. Groups B/C/D/E stay open, so this does not close the card.
What this is
Seven keys across five hand-written zod mirrors refused values the published
TypeScript declarations invite. That is
declared != enforcedon a publishedvalidator (
@object-ui/types/zod). The mirrors are widened to their declarations.Direction: the contract WIDENS. Nothing is narrowed, nothing previously
accepted is now rejected. For consumers that means no schema that parsed before
can stop parsing; the only visible effect is that
z.inferof these schemasgains members, so code that exhaustively switches on an inferred union (a
switchoverchartTypewith nodefault, say) will want the new arms. Thedownstream sweep below is green on that basis, not by assumption.
Why every row was measured instead of matched
Per the maintainer ruling recorded on #5927 (2026-08-24, verbatim
「四维分析一致的,接手你的建议。」), widening every mirror to its declaration is
refused even though it is the cheap option: it makes each DISJOINT pair accept
both spellings, so an AI-authored app could write the dead one, pass
validation, and render nothing — a silent failure the validator itself
endorses. So each key here was decided by reading the running code, per the
#4605 precedent. A spelling that appears only in a type union does not count.
navigation.zod.ts#ButtonGroupSchemavariantsecondary,destructive,ghost,linkbuttonVariantscvamap,components/src/ui/button.tsx:19-27— all six variants carry real class strings; the renderer passesbutton.variant || schema.variantstraight toButton(renderers/basic/button-group.tsx:35)navigation.zod.ts#ButtonGroupSchemasizeiconcvamap,size.icon = "h-10 w-10"(ui/button.tsx:33)objectql.zod.ts#ObjectChartSchemachartTypecolumn,horizontal-bar,donutAdvancedChartImpl.tsx:405,1398,1480normalizecolumn→bar;:626mapshorizontal-bartoBarChartwith:1228isHorizontaland the:1333layout branch;:630mapsdonuttoPieChartwith:904innerRadius = '52%';plugin-charts/src/index.tsx:163-169registers adonut-chartblockform.zod.ts#FormSchemavalidationModeonTouched,allrenderers/form/form.tsx:1103hands it verbatim to react-hook-form'suseForm({ mode }); RHF implements both as real branches —isOnTouch: mode === VALIDATION_MODE.onTouched/isOnAll: mode === VALIDATION_MODE.all(react-hook-form@7.85.0/dist/index.esm.mjs:676-677) read at:2004and:2007form.zod.ts#SelectSchemadefaultValue,valuebooleanrenderers/form/select.tsx:48-50,62—toControlValue(schema.defaultValue)stringifies a boolean to"true",SelectItem value={String(opt.value)}renders the matching option, andmatchOptionValuemaps it back to the authoredtrue(option-value.ts, the #3090 round-trip whoseOptionValuetype isstring | number | boolean)data-display.zod.ts#DataTableSchemaselectable'single','multiple'resolveSelectionMode(renderers/complex/data-table.tsx:592) —'single'is a real mode, not a truthy alias::1215replaces the selection set instead of extending it, and:1816renders the select-all header only for'multiple'views.zod.ts#ViewSwitcherSchema(viaViewTypeSchema)defaultView,activeView,views[].typechartplugin-list/src/ListView.tsx:2224andplugin-view/src/ObjectView.tsx:1228both carry acase 'chart':that builds anobject-chartnode;plugin-list/src/ViewSwitcher.tsx:73,100gives it an icon and a label key, andListView.tsx:2040switches on the value the switcher setsNo group A row measured dead, so this PR lands no narrowing — consistent
with the ruling that an ADR-0049 withdrawal is a separate breaking change.
One corroboration worth naming:
ButtonGroupButtonSchema(navigation.zod.ts:144-145)already carried the full six-variant / four-size vocabulary. Only the
group-level default was behind, and the renderer feeds both to the same
Button.Re-measurement, not the card's table
The card's table is stale by construction and its own first comment says so.
So #5684's derived construction was re-run over all 163 registered pairs
rather than read off the table, by generating one
neverassignment per pairagainst
DriftOf< pair >and reading the pairstscnames.11d3ab999: 17 of 163 drifted — the card's 17exactly, and
zod-mirror-parity.test.tsitself compiled with zero errors,which means the
KnownDriftledger equalled the measurement key-for-key inboth directions.
origin/maind2fb6efb4(i.e. afterfeat(types,components)!:ui:iconnames its glyph withicon, not the identity keyname#6012 landed, with no localedits, measured in a throwaway worktree): still 17 of 163, the same
pairs, ledger still green. feat(types,components)!:
ui:iconnames its glyph withicon, not the identity keyname#6012 moved the count by zero — measured here,not inherited from its PR's claim.
layout.zod.ts#IconSchemais not amongthe drifted pairs.
d2fb6efb4: 13 of 163.SPEC_DERIVED_PAIRSwas checked before editing: none of the seven group A rowsis spec-derived (that set is
AppComponentSchema,NavigationAreaSchema,BaseSchema,DashboardComponentSchema,DashboardWidgetSchema,SelectOptionSchema,PageNodeSchema,ObjectGanttSchema,ObjectMapSchema,ObjectViewSchema), so no spec-bound mirror is widened here. NoteSelectOptionSchemais spec-derived, but the keys touched areSelectSchema's owndefaultValue/value, not the option's.The ledger closes in the same PR
KnownDriftis shrink-only and pinned to each pair's exact drifted key set, soa corrected drift fails the guard until its row is corrected. Four entries left
outright (
SelectSchema,ButtonGroupSchema,ObjectChartSchema,ViewSwitcherSchema); two shrank to the keys that are not widenings —DataTableSchemakeepsrowActions,FormSchemakeepsfields/mode. GroupE (
PageNodeSchema.pageType) is untouched and stays ledgered with its reason.Non-vacuity, measured rather than assumed. With the widening in place, one
deleted row was put back and the ratchet turned red naming the exact pair:
So the row deletions are not masking a no-op widening.
Reverse verification
A probe authors one document per newly-admitted spelling and calls
safeParse.It imports the worktree source mirrors directly, not the package
exports,so a mutation on disk is visible with no rebuild step. Direction was predicted
before each run.
(spellings both sides already admitted —
variant: outline,chartType: bar,validationMode: onBlur,defaultValue: 1,selectable: true,defaultView: grid) all parsed. A probe that could not parse anything wouldlook identical to a narrow mirror; the controls rule that out.
TOTAL parses=23 refused=0.trap ... EXIT INT TERM, and only its own rows went red — 4 / 1 / 3 / 2 / 2 /2 / 3 across the seven legs, with all six controls still parsing in every leg.
Each mutation was proved on disk by grepping the injected text and
separately the removed text before the reading was taken (an editor's exit
code proves nothing on a zero-hit anchor).
git diff HEAD --statwas emptyafterwards.
Gates
Run on
956940b1b(the tree in this PR, merged withd2fb6efb4); exit codescaptured before any pipe.
pnpm --filter @object-ui/types type-check(tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json— this is where the parity ratchet lives)npx vitest run packages/types/src --maxWorkers=2— 55 files, 606 tests passedpnpm --workspace-concurrency=2 --filter '...@object-ui/types' type-check— the prefix form, i.e. downstream consumers; 42 projectsDonenpx vitest run packages/plugin-view/src/__tests__/ViewSwitcher.test.tsx packages/plugin-charts/src packages/plugin-list/src --maxWorkers=2— 82 filesThe downstream sweep needed
pnpm --filter '@object-ui/react-runtime...' buildfirst:
@object-ui/react-runtimedoes not depend on@object-ui/types, so the...@object-ui/typesclosure never builds it whilepackages/componentsrequires its
dist. That is a property of the filter, not of this change.Not in this PR
Groups B (6 disjoint-vocabulary rows) and C (2 required-vs-optional rows) are
write-up only under the ruling and go back to the decision inbox as one issue
with the renderer reading attached. Group D rides #2231. Group E stays
ledgered. #5853's opposite direction is untouched.
Generated by Claude Code