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
Implements the maintainer ruling of 2026-08-29 (verbatim 「同意」, keyed): ObjectField's Invalid JSON and LocationField's two refusal sentences go through useFieldTranslation + FIELD_DEFAULTS, with entries in all ten locale packs, bound from now on by check:i18n-drift.
The card's own line is the whole justification: "a zh / ja / ar user who mistypes a coordinate or a JSON blob is told why in English, inside a product whose labels, gate hints and validation copy are all translated". The defect is not that the string is English — AGENTS.md #-1 requires exactly that in the codebase. It is that a translatable surface was never routed through the channel that already existed beside it and that 11 of this package's 55 widgets already read.
What changed
key
en value
read by
fields.object.invalidJson
Invalid JSON
ObjectField's unparsable draft
fields.location.refusedFormat
Not saved: enter a latitude, longitude pair (example: 30.2741, 120.1551).
refusedRange keys the frame only. {{detail}} stays LocationValueSchema's own complaint, because the widget must not restate the spec's bounds — a hand-copied range is the second contract finding(fields): LocationField emits out-of-range coordinates the spec rejects #6714 exists to prevent. The zh/ja/ar tests pin exactly that division rather than pretending the whole sentence is translated.
The two LocationField producers now take the package's declared TranslateFn (exported by file-size-guard.ts, the shape FileField / ImageField already forward), imported rather than re-declared.
No allowlist entry was needed. All five non-Latin packs carry real translations, so untranslated-identity-4376's identity probe has nothing to excuse; the mechanism is used as-is, and the ruling's provision for Latin-identity spellings simply did not have to fire.
The #4028 precedent, read at the source rather than quoted second-hand
Triage flagged a confidence gap on itself (「我没有独立读 #4028」). AddressField's actual comment was read before writing any citation. It gives three reasons for dropping the five placeholders, quoted verbatim from the tree:
"Every example-data placeholder the packs DO carry teaches a FORMAT its label cannot" … "Each box here already has a visible label naming exactly what it wants";
"The right example is a function of the address's COUNTRY, not the reader's language";
"Keying costs 50 pack entries of invented fictional address fragments, bound from then on by check:i18n-drift".
Reasons 1 and 2 are category facts about example data and neither reaches a refusal sentence: nothing else on the screen says why the edit was refused, and what that sentence must say is a function of the reader's language. Reason 3 is a cost, which transfers, and which the ruling accepted for these three. So the line as the maintainer drew it holds at the source.
One correction to the record, since the point of reading it was to check rather than to confirm: #4028's comment does not anywhere say that the packs "should carry refusal sentences a user must read to recover". That phrasing entered through the filer's paraphrase and was repeated in triage. What #4028 concedes is narrower — that the packs carry format-teaching example placeholders. The distinction the ruling rests on survives without that sentence; the code comment added in en.ts quotes only what the source actually says.
Verification
Baseline is origin/main at 9ce20233f; every number below is from 4be2007b5, the head of this branch, run after the final commit.
Red first. The new pins were written and run against the untouched tree before any source edit: 9 failed | 8 passed (17), each failure the assertion expected 'Invalid JSON' to be 'JSON 无效' (and the two LocationField equivalents) — the defect, not a module error. After the change: 17 passed (17).
Positive control for the pack read. The same file renders AddressField under zh and asserts 街道地址 / 城市 — keys that already resolved through this channel before the card. It passed in the RED run too, which is what makes the 9 failures evidence about the three new keys rather than about a dead provider. Every negative assertion here ("no English literal survives") is otherwise satisfiable by a widget that renders nothing.
Delta is exactly the one new file and its 17 tests; nothing else moved, nothing failed on either side.
Gates, each quoting its own verdict line, all on 4be2007b5:
pnpm check:i18n-keys → exit 0 — "Every in-scope call-site key resolves against the en pack (2837 keys), every literal inline defaultValue matches the value the pack serves, every call site passes exactly the arguments that value has holes for …"
pnpm check:i18n-drift → exit 0 — "0 en value(s) changed (3 key(s) added, 0 removed — those are all-locales-key-parity's) … No en value changed in this range."
pnpm check:i18n-dead-keys → exit 0 (report-only). 362 candidates repo-wide, none of them these three keys; the objectui#6666 pack-property blind spot did not fire on keys reached by a literal t().
pnpm check:control-bytes → exit 0 — "✅ check-control-bytes: OK (scanned 5738 tracked text file(s); skipped 85 binary)."
pnpm changeset:check → exit 0 — "✅ All workspace packages are in the changeset fixed group." / "✅ No changeset declares a major bump."
pnpm --filter @object-ui/fields --filter @object-ui/i18n type-check → exit 0, both scripts echoed (packages/fields type-check$ tsc --noEmit && tsc -p tsconfig.test.json → Done), so a zero-match false green is excluded. tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked set (1 hit), i.e. the green covers it.
Lint, narrowed and the narrowing measured rather than assumed: eslint --no-inline-config --format json over the 14 changed .ts/.tsx files — 14 files in eslint's own reported population, 0 ignored, 0 errors, 9 warnings (all no-explicit-any / set-state-in-effect, the latter only because --no-inline-config suppresses the existing disable comments; CI does not pass that flag). Package-level, exactly what CI's pnpm lint runs for these two packages: pnpm --filter @object-ui/fields --filter @object-ui/i18n lint → exit 0, "✖ 912 problems (0 errors, 912 warnings)" and "✖ 34 problems (0 errors, 34 warnings)". The narrowing cannot hide anything: eslint.config.js configures no type-aware linting (no parserOptions.project / projectService), so a change in these files cannot move the verdict on a file this diff does not touch.
Not measured, and named as such rather than reported as green:pnpm check:readme-exports exits 1 locally with "the population COLLAPSED — packagesRead: found 10, floor is 25", because it reads exports from built dist/ and only this branch's dependency closure was built. The same command at the baseline commit fails the same way (found 1), and this diff adds no export and edits no README. pnpm check:eager-closure exits 2 with "Ceiling freshness: not applicable to a local run". Both are CI-owned.
Scope, and one thing the card could not have known
Scope is the three sentences the ruling names. #4752 (undoLabel / redoLabel in @object-ui/components) does not ride along and keeps its own card, as the ruling says explicitly. AddressField's five placeholders stay dropped, per #4028.
LocationField now has a third refusal sentence, though. #6715 added the residue arm (12abc, 34 → "latitude "12abc" is not a number. Enter plain decimals…") after this card was measured and after the ruling was written, so the ruled count of three sentences is one short of the tree. It is left as a literal here and filed as #6888 rather than folded in — reading a scope lock as "except when I find one more" is not this seat's call — with the consequence stated on that card: after this lands, one <p> element speaks the reader's language for two arms and English for the third. LocationField's residue producer carries a comment pointing at #6888, and no test in this PR pins that sentence's English text, which would read as endorsement. #6888 is not addressed here.
…the locale channel
`ObjectField`'s "Invalid JSON" and `LocationField`'s format and range refusals
were string literals in the widgets, inside a package whose locale channel 11 of
its 55 widgets already read. A zh/ja/ar user who mistyped a coordinate or a JSON
blob was told why in English, in a form whose labels, gate hints and validation
copy were all translated.
All three now read from `useFieldTranslation` / `FIELD_DEFAULTS` under
`fields.object.invalidJson`, `fields.location.refusedFormat` and
`fields.location.refusedRange`, with entries in all ten locale packs. The `en`
values are byte-identical to the literals they replace, so English and
provider-less rendering are unchanged.
`fields.location.refusedRange` keys the FRAME only: `{{detail}}` stays
`LocationValueSchema`'s own complaint, because the widget must not restate the
spec's bounds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Landing gated on CI. Two things here are worth more than the diff.
⭐ Reading #4028 at the source caught a false claim inside the ruling itself
I ordered this because triage had flagged a confidence gap on itself (「我没有独立读 #4028」) and
because a second-hand quote entering the tree is the failure this seat has hit repeatedly today.
It paid, and further than I expected.
The maintainer's ruling justifies keying these three by saying the refusal-sentence category is 「这正是 #4028 自己承认语言包应当承载的类别」 — that #4028 itself concedes the packs should carry
sentences a user must read to recover. Read at the source, #4028 does not say that. What it
concedes is narrower: that the packs carry format-teaching example placeholders. The broader
phrasing entered through the filer's paraphrase, was repeated by triage, and then appeared in the
ruling — and I repeated it again in my own claim comment on #6755.
So a claim nobody verified propagated through four hands and into a maintainer ruling. The
ruling's conclusion survives — and the PR is right about why: #4028's reasons 1 and 2 are
category facts about example data that do not reach a refusal sentence (nothing else on screen
says why the edit was refused, and what that sentence must say is a function of the reader's
language), while reason 3 is a cost, which does transfer and which the ruling knowingly
accepted. The line as the maintainer drew it holds; only its stated ground was borrowed.
Quoting only what the source actually says, in the en.ts comment, is exactly right. A comment
that overstates its precedent is the same planted-premise defect as #6883's spent any-erasure
justification.
The verification answers the trap this card was built on
A "translated lookup" test is satisfiable by a widget that renders nothing, and every negative
assertion here ("no English literal survives") has that hole. The PR closes it: AddressField
under zh asserting 街道地址 / 城市 — keys that already resolved through this channel
before the card — and it passed in the RED run too. That is what makes the 9 red failures
evidence about the three new keys rather than about a dead provider. Red 9 failed / 8 passed → 17
passed, delta exactly the one new file.
Also correct, and not asked for: refusedRange keys the frame only, leaving {{detail}} as LocationValueSchema's own complaint, because a hand-copied range would be the second contract #6714 exists to prevent. And no untranslated-identity-4376 entry was added — all five
non-Latin packs carry real translations, so the identity probe has nothing to excuse. The
ruling's provision for Latin-identity spellings simply did not have to fire; using a mechanism
only when it applies is better than using it because a ruling mentioned it.
check:i18n-dead-keys reporting 362 candidates with none of these three among them is the
right control to have named, given objectui#6666's known pack-property blind spot — these keys
are reached by a literal t(), so the blind spot could not fire on them.
check:readme-exports and check:eager-closure correctly reported as NOT MEASURED rather
than green or red — population COLLAPSED (10 read, floor 25) from a partially-built closure, and
the same failure reproduces at the baseline commit. That is the discipline, applied unprompted.
Scope: the third sentence was right to leave, and right to escalate
LocationField gained a third refusal arm from #6715after this card was measured
(2026-08-29 06:24) and after the ruling was written (14:53). Not folding it in was correct —
「范围锁定这 3 句」 — and the reasoning given is the one I want seats to use: "reading a scope
lock as 'except when I find one more' is not this seat's call." Filing #6888 with the
consequence stated, adding a source comment pointing at it, and deliberately not pinning the
residue arm's English text (which would read as endorsement) is the complete version of that
move.
I am taking the consequence seriously rather than filing it and moving on — see my note on #6888.
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.
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
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#6755
Implements the maintainer ruling of 2026-08-29 (verbatim 「同意」, keyed):
ObjectField'sInvalid JSONandLocationField's two refusal sentences go throughuseFieldTranslation+FIELD_DEFAULTS, with entries in all ten locale packs, bound from now on bycheck:i18n-drift.The card's own line is the whole justification: "a zh / ja / ar user who mistypes a coordinate or a JSON blob is told why in English, inside a product whose labels, gate hints and validation copy are all translated". The defect is not that the string is English — AGENTS.md #-1 requires exactly that in the codebase. It is that a translatable surface was never routed through the channel that already existed beside it and that 11 of this package's 55 widgets already read.
What changed
envaluefields.object.invalidJsonInvalid JSONObjectField's unparsable draftfields.location.refusedFormatNot saved: enter a latitude, longitude pair (example: 30.2741, 120.1551).LocationField, format arm (#6716)fields.location.refusedRangeNot saved: {{detail}}LocationField, range arm (#6714 / #6716)envalues are byte-identical to the literals they replace, so English and provider-less rendering are unchanged. finding(fields):LocationFieldrefuses input silently — the validation slot it wires has no producer #6716's and finding(fields):LocationFieldaccepts a partly-numeric coordinate, emitting a plausible wrong location #6715's pins, andplugin-form's two refusal suites, are untouched and still green.refusedRangekeys the frame only.{{detail}}staysLocationValueSchema's own complaint, because the widget must not restate the spec's bounds — a hand-copied range is the second contract finding(fields):LocationFieldemits out-of-range coordinates the spec rejects #6714 exists to prevent. The zh/ja/ar tests pin exactly that division rather than pretending the whole sentence is translated.LocationFieldproducers now take the package's declaredTranslateFn(exported byfile-size-guard.ts, the shapeFileField/ImageFieldalready forward), imported rather than re-declared.untranslated-identity-4376's identity probe has nothing to excuse; the mechanism is used as-is, and the ruling's provision for Latin-identity spellings simply did not have to fire.The #4028 precedent, read at the source rather than quoted second-hand
Triage flagged a confidence gap on itself (「我没有独立读 #4028」).
AddressField's actual comment was read before writing any citation. It gives three reasons for dropping the five placeholders, quoted verbatim from the tree:check:i18n-drift".Reasons 1 and 2 are category facts about example data and neither reaches a refusal sentence: nothing else on the screen says why the edit was refused, and what that sentence must say is a function of the reader's language. Reason 3 is a cost, which transfers, and which the ruling accepted for these three. So the line as the maintainer drew it holds at the source.
One correction to the record, since the point of reading it was to check rather than to confirm: #4028's comment does not anywhere say that the packs "should carry refusal sentences a user must read to recover". That phrasing entered through the filer's paraphrase and was repeated in triage. What #4028 concedes is narrower — that the packs carry format-teaching example placeholders. The distinction the ruling rests on survives without that sentence; the code comment added in
en.tsquotes only what the source actually says.Verification
Baseline is
origin/mainat9ce20233f; every number below is from4be2007b5, the head of this branch, run after the final commit.Red first. The new pins were written and run against the untouched tree before any source edit: 9 failed | 8 passed (17), each failure the assertion
expected 'Invalid JSON' to be 'JSON 无效'(and the twoLocationFieldequivalents) — the defect, not a module error. After the change: 17 passed (17).Positive control for the pack read. The same file renders
AddressFieldunderzhand asserts街道地址/城市— keys that already resolved through this channel before the card. It passed in the RED run too, which is what makes the 9 failures evidence about the three new keys rather than about a dead provider. Every negative assertion here ("no English literal survives") is otherwise satisfiable by a widget that renders nothing.Suites (
pnpm exec vitest run packages/fields/src packages/i18n/src packages/plugin-form/src, canonical repo-root invocation):9ce20233f, baseline worktree)4be2007b5)Delta is exactly the one new file and its 17 tests; nothing else moved, nothing failed on either side.
Gates, each quoting its own verdict line, all on
4be2007b5:pnpm check:i18n-keys→ exit 0 — "Every in-scope call-site key resolves against the en pack (2837 keys), every literal inline defaultValue matches the value the pack serves, every call site passes exactly the arguments that value has holes for …"pnpm check:i18n-drift→ exit 0 — "0 en value(s) changed (3 key(s) added, 0 removed — those are all-locales-key-parity's) … No en value changed in this range."pnpm check:i18n-dead-keys→ exit 0 (report-only). 362 candidates repo-wide, none of them these three keys; the objectui#6666 pack-property blind spot did not fire on keys reached by a literalt().pnpm check:control-bytes→ exit 0 — "✅ check-control-bytes: OK (scanned 5738 tracked text file(s); skipped 85 binary)."pnpm changeset:check→ exit 0 — "✅ All workspace packages are in the changeset fixed group." / "✅ No changeset declares amajorbump."pnpm --filter @object-ui/fields --filter @object-ui/i18n type-check→ exit 0, both scripts echoed (packages/fields type-check$ tsc --noEmit && tsc -p tsconfig.test.json→Done), so a zero-match false green is excluded.tsc -p tsconfig.test.json --listFilesconfirms the new test file is in the checked set (1 hit), i.e. the green covers it.pnpm check:spec-symbols,check:phantom-deps,check:self-import,check:esm-specifiers,check:vi-mock-specifiers,check:side-effects-array→ exit 0.Lint, narrowed and the narrowing measured rather than assumed:
eslint --no-inline-config --format jsonover the 14 changed.ts/.tsxfiles — 14 files in eslint's own reported population, 0 ignored, 0 errors, 9 warnings (allno-explicit-any/set-state-in-effect, the latter only because--no-inline-configsuppresses the existing disable comments; CI does not pass that flag). Package-level, exactly what CI'spnpm lintruns for these two packages:pnpm --filter @object-ui/fields --filter @object-ui/i18n lint→ exit 0, "✖ 912 problems (0 errors, 912 warnings)" and "✖ 34 problems (0 errors, 34 warnings)". The narrowing cannot hide anything:eslint.config.jsconfigures no type-aware linting (noparserOptions.project/projectService), so a change in these files cannot move the verdict on a file this diff does not touch.Not measured, and named as such rather than reported as green:
pnpm check:readme-exportsexits 1 locally with "the population COLLAPSED — packagesRead: found 10, floor is 25", because it reads exports from builtdist/and only this branch's dependency closure was built. The same command at the baseline commit fails the same way (found 1), and this diff adds no export and edits no README.pnpm check:eager-closureexits 2 with "Ceiling freshness: not applicable to a local run". Both are CI-owned.Scope, and one thing the card could not have known
Scope is the three sentences the ruling names. #4752 (
undoLabel/redoLabelin@object-ui/components) does not ride along and keeps its own card, as the ruling says explicitly.AddressField's five placeholders stay dropped, per #4028.LocationFieldnow has a third refusal sentence, though. #6715 added the residue arm (12abc, 34→ "latitude "12abc" is not a number. Enter plain decimals…") after this card was measured and after the ruling was written, so the ruled count of three sentences is one short of the tree. It is left as a literal here and filed as #6888 rather than folded in — reading a scope lock as "except when I find one more" is not this seat's call — with the consequence stated on that card: after this lands, one<p>element speaks the reader's language for two arms and English for the third.LocationField's residue producer carries a comment pointing at #6888, and no test in this PR pins that sentence's English text, which would read as endorsement. #6888 is not addressed here.Generated by Claude Code