fix(types,components): FieldValidationRules.pattern.value is RegExp-only; unrecognized rule names shout (#5099) - #5187
Merged
Conversation
…e names shout (#5099) Maintainer ruling 2026-08-18 (A + B's error half; B's normalization half rejected): - FieldValidationRules.pattern.value narrows from `string | RegExp` to `RegExp` on the hand-written surface. react-hook-form applies `pattern` only when `value instanceof RegExp`, so a string here type-checked and then validated NOTHING, silently. Strings stay legal on the metadata route (FieldSchema.pattern), which buildValidationRules compiles. - The form renderer's single read point now reports validation rule names react-hook-form does not run (dev-time console.error): misspelled `minlength`, invented `email`, numeric keys from spreading an array. Recognized set pinned against the INSTALLED react-hook-form bundle (rhf-recognized-rule-keys.test.ts re-derives it from dist/index.cjs.js). - Deliberately NOT compiling string patterns at the read point — the ruling rejected that consumer tolerance (AGENTS.md #0.1); a test pins the rejection in both directions. BREAKING for hand-written string patterns; declared `minor` per this repo's version-alignment policy (majors track @objectstack, enforced by check-changeset-no-major.mjs), with the breaking semantics stated in the changeset body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMHbqfPiETJHA95r6WzZWS
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
…e it PM patch round on PR #5187, two blocking changes: - Dev-only: the ruling's second limb says dev-time error; a customer's production console is not the audience. Same gate as basic/div.tsx / basic/span.tsx (warnDeprecatedOnce), including its order property: the production check returns BEFORE the seen-set is marked, so a production render never suppresses the later dev notice. - Once per field + rule-set: rawFields is a caller-owned prop, so a schema-driven caller rebuilding the array per render re-ran the effect every render and repeated the notice forever (objectui#3965 measured what that costs). Module-level Set memo keyed by field name + sorted offending keys, the warnOnTopLevelStyleUrl shape from plugin-map/ObjectMap.tsx. Message text, recognized-set extraction, bundle pin and type narrowing unchanged. Both new behaviors pinned by tests, gate order included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMHbqfPiETJHA95r6WzZWS
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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 #5099
Implements the maintainer ruling of 2026-08-18 (comment-5322881617, verbatim 「同意」), all three limbs:
1. A — the declaration narrows (producer-side strict)
FieldValidationRules.pattern.valueis nowRegExp— no longerstring | RegExp(packages/types/src/form.ts). react-hook-form's field validator appliespatternonly whenvalue instanceof RegExp(re-derived from the installed 7.85.0 bundle, see below), so a string here type-checked for years and then validated nothing, silently. A hand-written string pattern is now a compile-time error.stringsurvives only on the metadata route's field declaration (FieldSchema.pattern), whichbuildValidationRulesin@object-ui/fieldscompiles — that route is untouched (read-only reference in this PR).In-repo callers turning red: zero. Repo-wide sweep (turbo
type-check, 81/81 tasks) found no caller authoring a string pattern; the docs example (content/docs/plugins/plugin-form.mdx) andpackages/types/examples/login-form.tsalready pass RegExp literals.2. B's error half — unrecognized rule names shout
At the single read point's field list, the renderer now reports rule names react-hook-form does not run (
console.error, house style of the #3090 vocabulary diagnostic: the message doubles as the fix instruction). A misspelledminlength, an inventedemail, and the numeric keys left by spreading an array intovalidation(the #5075 group-1 shape) all shout instead of vanishing.The recognized set lives in
packages/components/src/renderers/form/validationRuleKeys.tsand is pinned against the installed react-hook-form bundle, not prose:rhf-recognized-rule-keys.test.tsre-derives the validator's fixed._fdestructure (ref, refs, required, maxLength, minLength, min, max, pattern, validate, name, valueAsNumber, mountin 7.85.0) fromdist/index.cjs.jsat test time with shape-based (minifier-safe) regexes, subtracts the descriptor internals, and requires exact set equality — a react-hook-form bump that changes the set turns the test red instead of silently rotting the diagnostic. Theinstanceof RegExpgate (the premise of limb 1) is pinned in the same file.3. B's normalization half — NOT implemented, and pinned that way
No
new RegExp(...)was added at the read point: the ruling explicitly rejected compiling strings there as consumer tolerance that would harden the ambiguous declaration into contract (AGENTS.md #0.1). This is pinned by test in both directions —unrecognized-validation-rules.test.tsxasserts a smuggled string pattern reaches react-hook-form uncompiled and validates nothing (submit passes), so if someone "helpfully" adds the normalization later, the suite goes red.Breaking change vs. the no-major gate — the deliberate reconciliation
This narrows a published type, and
scripts/check-changeset-no-major.mjsforbidsmajor. These do not conflict: this repo's version policy (AGENTS.md §版本号策略) reservesmajorexclusively for tracking@objectstackmajors ("major 相同即兼容") and directs objectui's own breaking changes to ship asminorwith the breaking semantics stated plainly in the body — which is also what the ruling ordered. The changeset (.changeset/field-validation-rules-pattern-regexp-5099.md) declaresminorfor@object-ui/types+@object-ui/componentsand states the breaking semantics up front: string patterns stop compiling, and red is the fix — every such caller was running zero validations already, so the error converts silent non-validation into explicit failure at authoring time.Verification (all at
0b27ef102)pnpm exec vitest run packages/components/src/renderers/form/ packages/types/— 80 files, 683 tests passed.pnpm --filter @object-ui/types type-checkand...components type-checkgreen; full consumer radiusturbo run type-check81/81 green (every package is downstream of@object-ui/types, so the full run covers the whole radius).check-changeset-presence/check-changeset-fixed/check-changeset-no-major/check:control-bytes/check:doc-types/check:phantom-deps/check:self-importgreen;check:doc-snippetsgreen after a full workspace build (doc snippets compile against the narrowed built types)..d.tsprobe: a temp components file with a string pattern →tscred withType 'string' is not assignable to type 'RegExp'→ probe deleted. Proves consumers read the rebuilt declaration, not a cache.form.tsfrom origin/main → typestsc -p tsconfig.test.jsonred (3 errors incl.TS2578: Unused '@ts-expect-error') → restored. This leg's reader is tsc over src (relative import), no dist in the path.form.tsxfrom origin/main → the two reporting tests red, the other three green as expected (they pin behavior identical on main: silence for recognized rules, RegExp path validating, strings not compiled), bundle-pin test unaffected → restored. Reader is vitest over src via relative imports, no dist in the path.Out of scope, filed
#5186 —
FormFieldSchema.validation's zod mirror (FieldConstraintsSchema) contradictsFieldValidationRulesin both directions (rejects the TS-legal object shape, accepts a flat shape the renderer never reads). Adjacent surface, measured evidence in the issue; not touched here.Generated by Claude Code