You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Maintainer ruling recorded — maintainer, 2026-08-18, batch acceptance of the triage seat's decision-inbox recommendations, verbatim: 「同意」. Provenance: triage session session_pm_triage_20260818T0104Z (objectstack#6015).
Ruled: A + B's error half; ⛔ not B's normalization half. Concretely: (1) FieldValidationRules.pattern.value narrows to RegExp on the hand-written surface — string stays only on the metadata route's field declaration, which buildValidationRules compiles; a hand-written string pattern becomes a compile-time error instead of a silently-unvalidated field. (2) The single read point additionally reports unrecognized rule names (dev-time error) so misspellings and invented rules shout instead of vanishing. (3) The normalization half of B is explicitly rejected — compiling strings at the read point would harden the ambiguous declaration into contract, the consumer-tolerance shape #0.1 names. Changeset per repo policy with the breaking semantics stated plainly in the body: callers writing string patterns turn red, which converts silent non-validation into explicit failure.
Label needs-user-decision → pm:queue in the same stroke.
Claim: PM loop round 1 (batch raised 3 → 4 on the maintainer's 2026-08-18 standing directive to prioritise bugs and UI/UX work — recorded on the seat post)
Session: session_01AMHbqfPiETJHA95r6WzZWS
Branch: claude/issue-5099-validation-rules-contract
Worktree: objectui-issue-5099
Domain: repo:objectui (execution seat; this repo has no domain:* lanes)
File surface: packages/types/src/form.ts (the FieldValidationRules declaration), packages/components/src/renderers/form/form.tsx (the single read point, ~:1652 / ~:1752), plus tests. packages/fields/src/index.tsx's buildValidationRules is read-only reference — the metadata route is correct and is not being changed (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: claude-fable-5
Clause-②: yes
Why Clause-②: yes and why fable — stating it because the path check alone would have missed it.dispatch-gates.mjs --tier reports no path-derived mandate for these paths, and this repo has no packages/spec/**, so the path limb is silent. But the content limb is not: the ruled fix changes the accept/reject behaviour of a published contract surface — FieldValidationRules.pattern.value narrows from string | RegExp to RegExp, so a hand-written declaration that compiles today becomes a compile error. That is clause ②'s content limb regardless of which directory it lives in, and clause ② is written as "changes accept/reject behaviour or widens the public surface" — narrowing counts. Dispatching at the contract-review tier up front, which is the cheaper end of being wrong here.
Serial constraints cleared: no open PR and no in-flight claim touches packages/types or packages/components. Disjoint from this round's other three claims (#5176app-shell, #5141fields, #5163plugin-map). Note packages/components last moved in PR #4933 (dad51e5dc, merged) — landed, not in flight.
The ruling is already made — implement it, do not re-open it
Maintainer ruling of 2026-08-18 (comment-5322881617, verbatim 「同意」) is binding and complete:
A —FieldValidationRules.pattern.value narrows to RegExp on the hand-written surface. string survives only on the metadata route's field declaration (FieldSchema.pattern), which buildValidationRules compiles.
B's error half — the single read point additionally reports unrecognized rule names at dev time, so misspellings (minlength) and invented rules (email) shout instead of vanishing.
⛔ B's normalization half is explicitly REJECTED. Do not compile string patterns at the read point. The ruling's reason, which is the point of the card: doing so would harden an ambiguous declaration into contract — the consumer-tolerance shape AGENTS.md #0.1 names. If you find yourself adding new RegExp(...) in form.tsx, you have implemented the rejected option.
Premise check — run now against origin/main @ 072085de, all three sites confirmed live
packages/types/src/form.ts:744 export interface FieldValidationRules {
packages/types/src/form.ts:942 validation?: FieldValidationRules;
packages/components/src/renderers/form/form.tsx ← the single read point ({ ...validation })
packages/fields/src/index.tsx ← buildValidationRules, the CORRECT metadata route
Line numbers inside those files are from the card and are not re-verified — take them from the tree.
Non-negotiables
The no-major gate is real (scripts/check-changeset-no-major.mjs). The ruling says: changeset per repo policy, with the breaking semantics stated plainly in the body. Narrowing a published type while the repo forbids a major bump is the delicate part of this card — solve it deliberately and explain the choice in the PR, do not paper over it.
Turning existing callers red is the intended outcome, not collateral: those callers are not validating anything today. If the narrowing turns in-repo callers red, fix them to pass RegExp — that is in surface. If it turns up a caller where a RegExp genuinely cannot be supplied, stop and report rather than widening the type back.
The console.error for unrecognized rule names must key off the set react-hook-form actually destructures (the card quotes it from the pinned 7.85.0 bundle). Derive that set from the installed version, and pin it so a react-hook-form bump that changes the set does not silently rot the diagnostic.
Do not touch the metadata route. It is correct.
Deliver
Draft PR against main, first line Fixes #5099. Push the branch early.
Return the structured JSON report AND post it as an issue comment whose first line is <!-- os-dev-report -->. Machine-checkable fields only — gates run + outcomes, files_changed, deviations, tests with real output.
{
"issue": 5099,
"status": "done",
"branch": "claude/issue-5099-validation-rules-contract",
"pr": "https://github.com/objectstack-ai/objectui/pull/5187",
"premise_still_valid": true,
"summary": "All three ruled limbs implemented at 0b27ef102: (A) FieldValidationRules.pattern.value narrowed to RegExp in packages/types/src/form.ts; (B-error) the form renderer reports unrecognized validation rule names via console.error, recognized set in validationRuleKeys.ts pinned against the installed react-hook-form 7.85.0 bundle by a test that re-derives the ._f destructure at test time; (B-normalization) NOT implemented per the ruling, and pinned by a test asserting string patterns reach react-hook-form uncompiled. Zero in-repo callers turned red. Card body checked for truncation: complete except the author-acknowledged stripped footer. Breaking-vs-no-major reconciled per AGENTS.md version policy: minor bump, breaking semantics stated in changeset body and PR.",
"files_changed": [
"packages/types/src/form.ts",
"packages/components/src/renderers/form/form.tsx",
"packages/components/src/renderers/form/validationRuleKeys.ts",
"packages/types/src/__tests__/field-validation-rules-pattern-regexp.test.ts",
"packages/components/src/renderers/form/__tests__/rhf-recognized-rule-keys.test.ts",
"packages/components/src/renderers/form/__tests__/unrecognized-validation-rules.test.tsx",
".changeset/field-validation-rules-pattern-regexp-5099.md"
],
"gates": [
"vitest new tests (3 files): 9 passed (9) — PASS",
"vitest packages/components/src/renderers/form/ + packages/types/ @ 0b27ef102: Test Files 80 passed (80), Tests 683 passed (683) — PASS",
"pnpm --filter @object-ui/types type-check (tsc --noEmit + examples + test tsconfigs): PASS",
"pnpm --filter @object-ui/components type-check: PASS",
"turbo run type-check (all packages, consumer radius): 81 successful, 81 total — PASS",
"turbo run lint --filter types --filter components: 3 successful, 3 total — PASS",
"check-changeset-presence.mjs: 6 src files of 2 released packages, 1 changeset declared — PASS",
"check-changeset-fixed.mjs + check-changeset-no-major.mjs: PASS",
"check:control-bytes: scanned 4596 tracked text files — PASS",
"check:doc-snippets (after full workspace build): 67 of 67 blocks judged, 0 failed — PASS",
"check:doc-types: PASS; check:phantom-deps: PASS; check:self-import: PASS",
"remote CI on PR #5187: in_progress at report time (report delivered at draft-PR time per contract)"
],
"tests": "New tests first run: 'Test Files 3 passed (3), Tests 9 passed (9)'. Union at final commit 0b27ef102: 'Test Files 80 passed (80), Tests 683 passed (683)'. Reverse verifications from committed state: (1) cross-package probe — temp components file with string pattern, tsc red: \"error TS2322: Type 'string' is not assignable to type 'RegExp'\", probe deleted; (2) type ablation — form.ts restored from origin/main, types tsc red with 3 errors incl. \"TS2578: Unused '@ts-expect-error' directive\", restored; reader is tsc over src, no dist in path, no rebuild needed; (3) diagnostic ablation — form.tsx restored from origin/main, observed '2 failed | 5 passed': the two reporting tests red, the three behavior-identical-on-main tests green (expected direction, not all-red), bundle-pin test green; reader is vitest over relative src imports, no dist in path; restored. The one dist-reading pair (components tsc reading types .d.ts) was exercised by leg 1 after a real dependency-closure build.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #5186: FormFieldSchema.validation's zod mirror (FieldConstraintsSchema) contradicts FieldValidationRules in both directions — rejects the TS-legal {value,message} object shape, accepts a flat shape the renderer never reads (measured against dist)"
],
"deviations": []
}
{
"issue": 5099,
"status": "done",
"branch": "claude/issue-5099-validation-rules-contract",
"pr": "https://github.com/objectstack-ai/objectui/pull/5187",
"premise_still_valid": true,
"summary": "PM patch round applied at d7b741f4f (on top of accepted 0b27ef102), both blocking changes: (1) the unrecognized-rule-name diagnostic is now dev-only — process.env.NODE_ENV === 'production' returns first, matching the warnDeprecatedOnce shape in basic/div.tsx and basic/span.tsx including its documented order property (production check returns BEFORE the seen-set is marked, so a production render never suppresses the later dev notice); (2) it reports once per field + rule-set via a module-level Set memo keyed fieldName + sorted offending keys — the warnOnTopLevelStyleUrl shape from plugin-map/ObjectMap.tsx on current main. Message text, RHF_RECOGNIZED_RULE_KEYS extraction, bundle pin test, type narrowing, changeset, and the metadata route all unchanged. Both new behaviors pinned by tests, gate order included. PR left as draft.",
"files_changed": [
"packages/components/src/renderers/form/form.tsx (patch round: dev gate + dedupe memo, effect body moved into module-level reportUnrecognizedRulesOnce)",
"packages/components/src/renderers/form/__tests__/unrecognized-validation-rules.test.tsx (patch round: +2 pins — production gate incl. order property, dedupe incl. keys-in-memo)"
],
"gates": [
"vitest packages/components/src/renderers/form/ + packages/types/ @ d7b741f4f: Test Files 80 passed (80), Tests 685 passed (685) — PASS",
"targeted 3 files (diagnostic behavior + bundle pin + type pin): 11 passed (11) — PASS",
"pnpm --filter @object-ui/types type-check: PASS; pnpm --filter @object-ui/components type-check: PASS",
"turbo run type-check (all packages, consumer radius): 81 successful, 81 total — PASS",
"turbo run lint --filter types --filter components: 3 successful, 3 total — PASS",
"check-changeset-presence.mjs: 6 src files of 2 released packages, 1 changeset — PASS; check-changeset-fixed.mjs: PASS; check-changeset-no-major.mjs: PASS",
"check:control-bytes: scanned 4596 tracked text files — PASS",
"remote CI on PR #5187: in_progress at report time (draft retained for PM re-review per patch-round instruction)"
],
"tests": "Updated suite at d7b741f4f: 'Test Files 80 passed (80), Tests 685 passed (685)' (was 683; +2 new pins). New-pin ablation from committed state: form.tsx restored to HEAD~1 (the accepted-but-ungated version) → exactly the two new tests red — '× is silent in production builds, and the gate does not eat the dev notice', '× reports once per field + rule-set, however often the caller rebuilds fields', '2 failed | 5 passed' — all pre-existing tests green, then restored from branch HEAD; reader is vitest over relative src imports, no dist in path, no rebuild needed. Gate-order property is itself pinned: production render first (0 messages), unstub env, rerender with fresh fields array → 1 message, which goes red if the seen-set is marked before the production return.",
"open_questions": [],
"out_of_scope_findings": [
"previously filed as #5186: FormFieldSchema.validation's zod mirror (FieldConstraintsSchema) contradicts FieldValidationRules in both directions (unchanged this round)"
],
"deviations": [
"check:doc-snippets not re-run this round: the patch touches only components renderer source and a test; doc snippets compile against built types, which are byte-identical to the run that passed 67/67 at 0b27ef102"
]
}
ACCEPT after one patch round — PR #5187 (repo:objectui seat PM, session session_01AMHbqfPiETJHA95r6WzZWS, reviewer of record).
The patch round, and the thing it found that I did not ask for
I sent this back for two blocking changes: gate the new diagnostic to non-production (the ruling said dev-time, the implementation fired unconditionally), and stop it re-firing per render. Both applied at d7b741f4f, no argument — and I checked, the argument would not have won.
What matters is what it did beyond the instruction. I pointed it at basic/div.tsx / basic/span.tsx as the in-package idiom. It matched not just the shape but the documented ordering property those files carry:
if (process.env.NODE_ENV === 'production') return; // BEFORE the memo is marked
if (_reportedUnrecognizedRules.has(memo)) return;
_reportedUnrecognizedRules.add(memo);
div.tsx's own comment explains why that order is load-bearing — "the production check returns BEFORE the seen-set is marked, so a production render never suppresses the dev notice" — and records the incident behind it (a browser-verification run that lost the signal it was looking for). Mark-then-return would poison the memo in a production render and silently cost the dev the warning. I did not specify this; it read the precedent's reasoning rather than copying its syntax, and pinned the ordering with a test (production render → 0 messages → unstub env → rerender → 1 message, red if the order flips). That is the difference between following an instruction and understanding it.
The ruled limbs, verified in the diff rather than from the report
A —FieldValidationRules.pattern.value is { value: RegExp; message: string }. Confirmed, andFieldSchema.pattern: string is untouched — the metadata route the ruling explicitly protected still takes a string and still compiles it via buildValidationRules.
B, error half — unrecognized rule names reported against RHF_RECOGNIZED_RULE_KEYS, extracted to validationRuleKeys.ts and pinned against the installed react-hook-form bundle by a test that re-derives the ._f destructure at test time, so a future bump cannot silently rot the diagnostic.
B, normalization half — correctly ABSENT. I grepped: there is no new RegExp anywhere in form.tsx. The rejected option stayed rejected, and is itself pinned by a test asserting string patterns reach react-hook-form uncompiled. Pinning the absence of a rejected option is unusual and right — it is what stops a later well-meaning refactor from "helpfully" adding tolerance back.
The message text is the other quiet strength: it doubles as the fix instruction, naming the correct spelling, the RegExp requirement, and the array-spread trap. That matters precisely because limb A only protects TypeScript authors — and the metadata that reaches this renderer as plain JSON never met a compiler. The type guards humans; the diagnostic guards generated metadata.
Semver — the genuinely hard part, solved by reading policy rather than dodging it
Narrowing a published type while check-changeset-no-major.mjs forbids a major is a real conflict. It shipped minor with the breaking semantics spelled out in the changeset body, and I verified the justification independently rather than accepting it: AGENTS.md §203–207 does say objectui's major is pinned to @objectstack's across a 39-package fixed group, and that "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)". The changeset does exactly that, and states what breaks, why red is the fix rather than the damage, and what is unaffected. Correct by policy, not by workaround.
Zero in-repo callers turned red, which is itself the finding that the old type was decorative: every caller was already passing a RegExp or not validating at all.
Evidence
Three reverse verifications, each with the right reader named — a cross-package probe proving consumers read the rebuilt .d.ts after a real dependency-closure build, a type ablation, and a diagnostic ablation. Two of them explicitly confirm no dist sits in the path, which is the failure mode that makes a green ablation meaningless. Plus a fresh ablation this round isolating exactly the two new pins.
Clause ②
Declared Clause-②: yes at claim time and dispatched at claude-fable-5 — which isCONTRACT_REVIEW_TIER. So the enqueue gate does not bind: the dispatch tier was at the floor, not below it, and no needs:contract-review hold is owed. Recording this because it is the case the gate is designed to produce — paying the tier up front on a card that changes a contract's accept/reject behaviour costs one dispatch and avoids a blocked queue entry later.
Landing: path surface is packages/types/** + packages/components/** + changeset — no governed face, queue-eligible. CI re-running for d7b741f4f (11 terminal, 10 in progress). Flip and enqueue on terminal success of every required job, read by name.
Out-of-scope finding #5186 stands from the first round, unchanged.
Activity
yinlianghui commentedon Aug 17, 2026
立卡人补记(issues 端点会剥掉正文末尾 footer,改用评论补):本卡由 #5075 的 os-dev 席在实施中扫出并如实分离 —— 那一单纯文档,不动
packages/types与packages/components;README 侧的处置是教能跑的那种拼法(hand-authoredpattern.value写 RegExp),把类型/运行时的契约选择留给本卡。未认领。Generated by Claude Code
yinlianghui commentedon Aug 18, 2026
PM 分诊(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片):挂
bug+needs-user-decision,入决策箱。手写FieldValidationRules在唯一读点既不校验也不归一(pattern.value为 string 时被 react-hook-form 静默忽略、未识别规则名静默丢弃),而类型声明明确允许 string —— 收敛方向是公开契约题:ObjectGridSchema.columns上容忍未声明的accessorKey/header拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068 / object-grid 声明的data输入类型是array(内联行),而契约类型是ViewData(对象)—— 照设计器写出的元数据 spec 解析不过 #5090 / ObjectGrid 用(schema as any)读的 4 个键不在GRID_QUERY_INPUTS里 —— #4648 要消除的「渲染器读得到、声明面否认」在这些键上仍然存在 #5091 / ObjectView 用(schema as any)从 object-view 节点读 31 个键转发给 renderListView,其中 27 个不是 ObjectViewSchema 声明成员 —— #5091 在 ObjectView 上的同形实例 #5097 同属「strict 声明 + 运行时另一套」家族,裁定时宜同读 —— 家族收敛方向一次定调可以少裁四次。在裁定落地前,README 侧(PR #5100 已落)按已实测事实教「hand-authored schema 传 RegExp」,不预断本卡方向。
Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
Maintainer ruling recorded — maintainer, 2026-08-18, batch acceptance of the triage seat's decision-inbox recommendations, verbatim: 「同意」. Provenance: triage session
session_pm_triage_20260818T0104Z(objectstack#6015).Ruled: A + B's error half; ⛔ not B's normalization half. Concretely: (1)
FieldValidationRules.pattern.valuenarrows toRegExpon the hand-written surface —stringstays only on the metadata route's field declaration, whichbuildValidationRulescompiles; a hand-written string pattern becomes a compile-time error instead of a silently-unvalidated field. (2) The single read point additionally reports unrecognized rule names (dev-time error) so misspellings and invented rules shout instead of vanishing. (3) The normalization half of B is explicitly rejected — compiling strings at the read point would harden the ambiguous declaration into contract, the consumer-tolerance shape #0.1 names. Changeset per repo policy with the breaking semantics stated plainly in the body: callers writing string patterns turn red, which converts silent non-validation into explicit failure.Label
needs-user-decision→pm:queuein the same stroke.Generated by Claude Code
os-steve commentedon Aug 18, 2026
Claim: PM loop round 1 (batch raised 3 → 4 on the maintainer's 2026-08-18 standing directive to prioritise bugs and UI/UX work — recorded on the seat post)
Session:
session_01AMHbqfPiETJHA95r6WzZWSBranch:
claude/issue-5099-validation-rules-contractWorktree:
objectui-issue-5099Domain:
repo:objectui(execution seat; this repo has nodomain:*lanes)File surface:
packages/types/src/form.ts(theFieldValidationRulesdeclaration),packages/components/src/renderers/form/form.tsx(the single read point, ~:1652 / ~:1752), plus tests.packages/fields/src/index.tsx'sbuildValidationRulesis read-only reference — the metadata route is correct and is not being changed (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: claude-fable-5Clause-②: yes
Why
Clause-②: yesand why fable — stating it because the path check alone would have missed it.dispatch-gates.mjs --tierreports no path-derived mandate for these paths, and this repo has nopackages/spec/**, so the path limb is silent. But the content limb is not: the ruled fix changes the accept/reject behaviour of a published contract surface —FieldValidationRules.pattern.valuenarrows fromstring | RegExptoRegExp, so a hand-written declaration that compiles today becomes a compile error. That is clause ②'s content limb regardless of which directory it lives in, and clause ② is written as "changes accept/reject behaviour or widens the public surface" — narrowing counts. Dispatching at the contract-review tier up front, which is the cheaper end of being wrong here.Serial constraints cleared: no open PR and no in-flight claim touches
packages/typesorpackages/components. Disjoint from this round's other three claims (#5176app-shell, #5141fields, #5163plugin-map). Notepackages/componentslast moved in PR #4933 (dad51e5dc, merged) — landed, not in flight.The ruling is already made — implement it, do not re-open it
Maintainer ruling of 2026-08-18 (comment-5322881617, verbatim 「同意」) is binding and complete:
FieldValidationRules.pattern.valuenarrows toRegExpon the hand-written surface.stringsurvives only on the metadata route's field declaration (FieldSchema.pattern), whichbuildValidationRulescompiles.minlength) and invented rules (email) shout instead of vanishing.new RegExp(...)inform.tsx, you have implemented the rejected option.Premise check — run now against
origin/main@072085de, all three sites confirmed liveLine numbers inside those files are from the card and are not re-verified — take them from the tree.
Non-negotiables
scripts/check-changeset-no-major.mjs). The ruling says: changeset per repo policy, with the breaking semantics stated plainly in the body. Narrowing a published type while the repo forbids a major bump is the delicate part of this card — solve it deliberately and explain the choice in the PR, do not paper over it.RegExp— that is in surface. If it turns up a caller where aRegExpgenuinely cannot be supplied, stop and report rather than widening the type back.console.errorfor unrecognized rule names must key off the set react-hook-form actually destructures (the card quotes it from the pinned 7.85.0 bundle). Derive that set from the installed version, and pin it so a react-hook-form bump that changes the set does not silently rot the diagnostic.Deliver
Draft PR against
main, first lineFixes #5099. Push the branch early.Return the structured JSON report AND post it as an issue comment whose first line is
<!-- os-dev-report -->. Machine-checkable fields only — gates run + outcomes, files_changed, deviations, tests with real output.Generated by Claude Code
fix(types,components): pattern.value is RegExp-only; unrecognized rul…
os-steve commentedon Aug 18, 2026
{ "issue": 5099, "status": "done", "branch": "claude/issue-5099-validation-rules-contract", "pr": "https://github.com/objectstack-ai/objectui/pull/5187", "premise_still_valid": true, "summary": "All three ruled limbs implemented at 0b27ef102: (A) FieldValidationRules.pattern.value narrowed to RegExp in packages/types/src/form.ts; (B-error) the form renderer reports unrecognized validation rule names via console.error, recognized set in validationRuleKeys.ts pinned against the installed react-hook-form 7.85.0 bundle by a test that re-derives the ._f destructure at test time; (B-normalization) NOT implemented per the ruling, and pinned by a test asserting string patterns reach react-hook-form uncompiled. Zero in-repo callers turned red. Card body checked for truncation: complete except the author-acknowledged stripped footer. Breaking-vs-no-major reconciled per AGENTS.md version policy: minor bump, breaking semantics stated in changeset body and PR.", "files_changed": [ "packages/types/src/form.ts", "packages/components/src/renderers/form/form.tsx", "packages/components/src/renderers/form/validationRuleKeys.ts", "packages/types/src/__tests__/field-validation-rules-pattern-regexp.test.ts", "packages/components/src/renderers/form/__tests__/rhf-recognized-rule-keys.test.ts", "packages/components/src/renderers/form/__tests__/unrecognized-validation-rules.test.tsx", ".changeset/field-validation-rules-pattern-regexp-5099.md" ], "gates": [ "vitest new tests (3 files): 9 passed (9) — PASS", "vitest packages/components/src/renderers/form/ + packages/types/ @ 0b27ef102: Test Files 80 passed (80), Tests 683 passed (683) — PASS", "pnpm --filter @object-ui/types type-check (tsc --noEmit + examples + test tsconfigs): PASS", "pnpm --filter @object-ui/components type-check: PASS", "turbo run type-check (all packages, consumer radius): 81 successful, 81 total — PASS", "turbo run lint --filter types --filter components: 3 successful, 3 total — PASS", "check-changeset-presence.mjs: 6 src files of 2 released packages, 1 changeset declared — PASS", "check-changeset-fixed.mjs + check-changeset-no-major.mjs: PASS", "check:control-bytes: scanned 4596 tracked text files — PASS", "check:doc-snippets (after full workspace build): 67 of 67 blocks judged, 0 failed — PASS", "check:doc-types: PASS; check:phantom-deps: PASS; check:self-import: PASS", "remote CI on PR #5187: in_progress at report time (report delivered at draft-PR time per contract)" ], "tests": "New tests first run: 'Test Files 3 passed (3), Tests 9 passed (9)'. Union at final commit 0b27ef102: 'Test Files 80 passed (80), Tests 683 passed (683)'. Reverse verifications from committed state: (1) cross-package probe — temp components file with string pattern, tsc red: \"error TS2322: Type 'string' is not assignable to type 'RegExp'\", probe deleted; (2) type ablation — form.ts restored from origin/main, types tsc red with 3 errors incl. \"TS2578: Unused '@ts-expect-error' directive\", restored; reader is tsc over src, no dist in path, no rebuild needed; (3) diagnostic ablation — form.tsx restored from origin/main, observed '2 failed | 5 passed': the two reporting tests red, the three behavior-identical-on-main tests green (expected direction, not all-red), bundle-pin test green; reader is vitest over relative src imports, no dist in path; restored. The one dist-reading pair (components tsc reading types .d.ts) was exercised by leg 1 after a real dependency-closure build.", "open_questions": [], "out_of_scope_findings": [ "filed as #5186: FormFieldSchema.validation's zod mirror (FieldConstraintsSchema) contradicts FieldValidationRules in both directions — rejects the TS-legal {value,message} object shape, accepts a flat shape the renderer never reads (measured against dist)" ], "deviations": [] }Generated by Claude Code
Generated by Claude Code
fix(components): gate the #5099 rule-name diagnostic to dev and dedup…
os-steve commentedon Aug 18, 2026
{ "issue": 5099, "status": "done", "branch": "claude/issue-5099-validation-rules-contract", "pr": "https://github.com/objectstack-ai/objectui/pull/5187", "premise_still_valid": true, "summary": "PM patch round applied at d7b741f4f (on top of accepted 0b27ef102), both blocking changes: (1) the unrecognized-rule-name diagnostic is now dev-only — process.env.NODE_ENV === 'production' returns first, matching the warnDeprecatedOnce shape in basic/div.tsx and basic/span.tsx including its documented order property (production check returns BEFORE the seen-set is marked, so a production render never suppresses the later dev notice); (2) it reports once per field + rule-set via a module-level Set memo keyed fieldName + sorted offending keys — the warnOnTopLevelStyleUrl shape from plugin-map/ObjectMap.tsx on current main. Message text, RHF_RECOGNIZED_RULE_KEYS extraction, bundle pin test, type narrowing, changeset, and the metadata route all unchanged. Both new behaviors pinned by tests, gate order included. PR left as draft.", "files_changed": [ "packages/components/src/renderers/form/form.tsx (patch round: dev gate + dedupe memo, effect body moved into module-level reportUnrecognizedRulesOnce)", "packages/components/src/renderers/form/__tests__/unrecognized-validation-rules.test.tsx (patch round: +2 pins — production gate incl. order property, dedupe incl. keys-in-memo)" ], "gates": [ "vitest packages/components/src/renderers/form/ + packages/types/ @ d7b741f4f: Test Files 80 passed (80), Tests 685 passed (685) — PASS", "targeted 3 files (diagnostic behavior + bundle pin + type pin): 11 passed (11) — PASS", "pnpm --filter @object-ui/types type-check: PASS; pnpm --filter @object-ui/components type-check: PASS", "turbo run type-check (all packages, consumer radius): 81 successful, 81 total — PASS", "turbo run lint --filter types --filter components: 3 successful, 3 total — PASS", "check-changeset-presence.mjs: 6 src files of 2 released packages, 1 changeset — PASS; check-changeset-fixed.mjs: PASS; check-changeset-no-major.mjs: PASS", "check:control-bytes: scanned 4596 tracked text files — PASS", "remote CI on PR #5187: in_progress at report time (draft retained for PM re-review per patch-round instruction)" ], "tests": "Updated suite at d7b741f4f: 'Test Files 80 passed (80), Tests 685 passed (685)' (was 683; +2 new pins). New-pin ablation from committed state: form.tsx restored to HEAD~1 (the accepted-but-ungated version) → exactly the two new tests red — '× is silent in production builds, and the gate does not eat the dev notice', '× reports once per field + rule-set, however often the caller rebuilds fields', '2 failed | 5 passed' — all pre-existing tests green, then restored from branch HEAD; reader is vitest over relative src imports, no dist in path, no rebuild needed. Gate-order property is itself pinned: production render first (0 messages), unstub env, rerender with fresh fields array → 1 message, which goes red if the seen-set is marked before the production return.", "open_questions": [], "out_of_scope_findings": [ "previously filed as #5186: FormFieldSchema.validation's zod mirror (FieldConstraintsSchema) contradicts FieldValidationRules in both directions (unchanged this round)" ], "deviations": [ "check:doc-snippets not re-run this round: the patch touches only components renderer source and a test; doc snippets compile against built types, which are byte-identical to the run that passed 67/67 at 0b27ef102" ] }Generated by Claude Code
Generated by Claude Code
os-steve commentedon Aug 18, 2026
ACCEPT after one patch round — PR #5187 (
repo:objectuiseat PM, sessionsession_01AMHbqfPiETJHA95r6WzZWS, reviewer of record).The patch round, and the thing it found that I did not ask for
I sent this back for two blocking changes: gate the new diagnostic to non-production (the ruling said dev-time, the implementation fired unconditionally), and stop it re-firing per render. Both applied at
d7b741f4f, no argument — and I checked, the argument would not have won.What matters is what it did beyond the instruction. I pointed it at
basic/div.tsx/basic/span.tsxas the in-package idiom. It matched not just the shape but the documented ordering property those files carry:div.tsx's own comment explains why that order is load-bearing — "the production check returns BEFORE the seen-set is marked, so a production render never suppresses the dev notice" — and records the incident behind it (a browser-verification run that lost the signal it was looking for). Mark-then-return would poison the memo in a production render and silently cost the dev the warning. I did not specify this; it read the precedent's reasoning rather than copying its syntax, and pinned the ordering with a test (production render → 0 messages → unstub env → rerender → 1 message, red if the order flips). That is the difference between following an instruction and understanding it.The ruled limbs, verified in the diff rather than from the report
FieldValidationRules.pattern.valueis{ value: RegExp; message: string }. Confirmed, andFieldSchema.pattern: stringis untouched — the metadata route the ruling explicitly protected still takes a string and still compiles it viabuildValidationRules.RHF_RECOGNIZED_RULE_KEYS, extracted tovalidationRuleKeys.tsand pinned against the installed react-hook-form bundle by a test that re-derives the._fdestructure at test time, so a future bump cannot silently rot the diagnostic.new RegExpanywhere inform.tsx. The rejected option stayed rejected, and is itself pinned by a test asserting string patterns reach react-hook-form uncompiled. Pinning the absence of a rejected option is unusual and right — it is what stops a later well-meaning refactor from "helpfully" adding tolerance back.The message text is the other quiet strength: it doubles as the fix instruction, naming the correct spelling, the RegExp requirement, and the array-spread trap. That matters precisely because limb A only protects TypeScript authors — and the metadata that reaches this renderer as plain JSON never met a compiler. The type guards humans; the diagnostic guards generated metadata.
Semver — the genuinely hard part, solved by reading policy rather than dodging it
Narrowing a published type while
check-changeset-no-major.mjsforbids a major is a real conflict. It shippedminorwith the breaking semantics spelled out in the changeset body, and I verified the justification independently rather than accepting it: AGENTS.md §203–207 does say objectui's major is pinned to@objectstack's across a 39-package fixed group, and that "objectui 自身的破坏性变更也标minor(在正文里写清 breaking 语义即可)". The changeset does exactly that, and states what breaks, why red is the fix rather than the damage, and what is unaffected. Correct by policy, not by workaround.Zero in-repo callers turned red, which is itself the finding that the old type was decorative: every caller was already passing a RegExp or not validating at all.
Evidence
Three reverse verifications, each with the right reader named — a cross-package probe proving consumers read the rebuilt
.d.tsafter a real dependency-closure build, a type ablation, and a diagnostic ablation. Two of them explicitly confirm nodistsits in the path, which is the failure mode that makes a green ablation meaningless. Plus a fresh ablation this round isolating exactly the two new pins.Clause ②
Declared
Clause-②: yesat claim time and dispatched atclaude-fable-5— which isCONTRACT_REVIEW_TIER. So the enqueue gate does not bind: the dispatch tier was at the floor, not below it, and noneeds:contract-reviewhold is owed. Recording this because it is the case the gate is designed to produce — paying the tier up front on a card that changes a contract's accept/reject behaviour costs one dispatch and avoids a blocked queue entry later.Landing: path surface is
packages/types/**+packages/components/**+ changeset — no governed face, queue-eligible. CI re-running ford7b741f4f(11 terminal, 10 in progress). Flip and enqueue on terminal success of every required job, read by name.Out-of-scope finding #5186 stands from the first round, unchanged.
Generated by Claude Code
docs(blocks): teach a validation shape the input node actually declar…
docs(plugin-form): fix pattern.value Type column to RegExp-only
fix(types,components): FieldValidationRules.pattern.value is RegExp-o…
docs(blocks): teach a validation shape the input node actually declar…
docs(plugin-form): fix pattern.value Type column to RegExp-only (obje…
fix(types): the 58 `on*` handler keys declared `z.function()` refuse …
fix(types): the 58 `on*` handler keys declared `z.function()` refuse …