Skip to content

fix(fields,i18n): key LocationField's residue refusal sentence, answering arity from precedent - #8150

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-6888-location-residue-i18n
Sep 6, 2026
Merged

fix(fields,i18n): key LocationField's residue refusal sentence, answering arity from precedent#8150
os-justin merged 2 commits into
mainfrom
claude/issue-6888-location-residue-i18n

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #6888

LocationField's THIRD refusal sentence — the residue arm objectui#6715 added — now reaches the locale packs, like the three objectui#6755 keyed.

Triage ruled this in-lane execution rather than a new adjudication (comment 5547164710): #6755's 「范围锁定这 3 句」 was a PR-sprawl constraint written 2026-08-29 14:53, and #6715 added this arm afterwards, so it is new terrain rather than excluded terrain. Triage's overturn condition — reading that scope lock as deliberately freezing the tree — was considered and not exercised: nothing in #6755's PR, its tests, or the source comments freezes anything. LocationField.tsx itself carried the opposite instruction in a comment, naming this card and the open question by number.

The defect this removes

All three refusal arms render through the same p element and the same refusalError state (LocationField.tsx, one refusalError && ... render site). Verified, not assumed. So after #6755 landed, that one line spoke the reader's language when the format or range arm fired and English when the residue arm did — objectui#4028's reported shape compressed into a single sentence position.

⭐ The arity question, answered with a survey rather than defaulted

The card, triage and the dispatch all flagged one thing as a real authoring choice: should named / verb be interpolation holes, or should the key split per arity? I was told to measure what this channel already does, follow a precedent if one exists and say which widget, and stop and report if there is none.

There is a precedent, it is explicit, and it is documented in source with its reason. This repo's plural convention is two sibling keys, X and XOne, branched at the call site — never i18next's _one/_other suffixes.

Instance Where
lookup.recordCount / lookup.recordCountOne RecordPickerDialog.tsx:1209the same package and the same FIELD_DEFAULTS map this card writes into
list.recordCount / list.recordCountOne plugin-list/src/ListView.tsx:4228
detail.reactionCount / detail.reactionCountOne plugin-detail/src/ReactionPicker.tsx:55
detail.relatedRecords / detail.relatedRecordOne named by that same comment

ReactionPicker.tsx:55 states the reason in the source:

Two keys, NOT an i18next _one/_other pair — this repo's own plural convention. A _one suffix would break all-locales-key-parity: zh/ja/ko have no separate singular form, so those packs would legitimately omit the _one half and the gate would read that as a missing key (objectstack#5430).

all-locales-key-parity.test.ts adds a second, independent reason (objectui#3863): an i18next plural family also needs a base key, because ru has four CLDR categories and ar six, no pack in this repo defines _few/_many/_two/_zero, and those categories silently fall back to English. The sibling pair has neither problem.

So: precedent followed, RecordPickerDialog's. Keys are fields.location.refusedResidue (both halves) and fields.location.refusedResidueOne (one half), picked at the call site.

The same reasoning is carried one step further for the parts the card called out as also translatable:

  • The verb is gone from the code. It was is not a number / are not numbers, chosen in TypeScript. It now lives inside each pack's own two values. ar uses its dualليسا رقمين for two halves against ليس رقمًا for one — which is exactly the form an English verb through a hole could never have produced, and it is pinned by name in the test.
  • The conjunction is gone too. ' and ' was English grammar joining the halves; each pack now writes its own inside the two-half value.
  • The coordinate nouns are keyed, fields.location.latitude / fields.location.longitude — once each, interpolated into both arities, so no locale holds two spellings of the same word. Every pack already spelled them inside its own refusedFormat (纬度/经度, широта/долгота, خط العرض/خط الطول), so this is not new translation work.

The only holes carrying untranslated data are {{text}} / {{otherText}} — the characters the person actually typed.

Widget population, re-measured with a control

The card and the routing comment both say 11 widgets; the dispatch measured 21 files and told me not to carry the 11 forward. Both numbers are answering different questions, and neither is the count of widgets:

  • 21 files under packages/fields/src/widgets/ match useFieldTranslation — of which 7 are .test. files and 1 is useFieldTranslation.ts itself, the definition.
  • 13 non-test .tsx consumers remain.
  • 9 of those are reachable from the fieldWidgetMap registry, i.e. actually renderable field types: AddressField, FilterConditionField, LocationField, LookupField, ObjectField, ObjectRefField, RecipientPickerField, SelectField, TagsField.
  • The other 4 are helper components, not registered widgets: CapabilityMultiSelectField, OptionsEmptyState, PeoplePicker, RecordPickerDialog.

Controls for that read, in the same pass: grepping the identical corpus for createSafeTranslation returns 7 files rather than 21 (so the corpus is not matching everything), and LocationField is present in the 21 (so it is not matching nothing). Registry denominator: the map exposes 45 type keys over 42 distinct widget files.

⭐ The precedent the arity answer rests on comes from RecordPickerDialog — one of the four non-registered helpers — so the answer would have been missed by a survey that counted only the 9 registered widgets.

Clause-② re-measured, and it agrees with the PM's assessment

  • English byte-identical: check:i18n-drift reports 0 en value(s) changed (4 key(s) added, 0 removed) — the gate's own words, not my claim. Both arities are additionally asserted byte-for-byte in the new test, and objectui#6715's LocationField.strictNumeric.test.tsx and plugin-form's ObjectForm.locationResidue.test.tsx pass untouched.
  • No schema accept/reject movement: nothing here touches parsing, LocationValueSchema, or the refusal decision. parseDraft is unchanged apart from ResidueHalf.label narrowing from string to the COORDINATE_LABELS union — a tightening internal to the module, so a future half cannot silently select the wrong noun key.
  • No exported symbol added or removed: refusedResidueMessage and coordinateName are both module-private.

One ratchet moved, deliberately

packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts counts matched „…“ spans in the de pack: 59 → 62, rdq still 0. My three added German values contribute exactly three matched pairs (one in the one-half sentence, two in the two-half one); the two noun keys carry no quotes at all. Updated in the file's own idiom, with the same per-card note every prior entry carries. rdq staying at 0 is the load-bearing half and it did.

Tests

  • New: packages/fields/src/__tests__/LocationField.residueI18n-6888.test.tsx (26 tests). Shape inherited from widget-diagnostics-i18n-6755.test.tsx: positive and negative assertions together (the English default is the fallback, so "says the Chinese thing" alone cannot tell keyed from hard-coded), plus that file's AddressField zh control so a dead provider cannot read as a pass. Asserts both arities in zh/ja/ar, both arities byte-identical in en and provider-less, the absence of all five English fragments (is not a number, are not numbers, ' and ', latitude, longitude), that every pack's two arity values differ in its own language, the ar dual by name, and that one p still serves all arms.
  • Green: packages/fields full suite 136 files / 2220 tests; packages/i18n + plugin-form's residue suite + app-shell's defaults-maps-mirror-en-pack 64 files / 1065 tests; type-check for both @object-ui/fields and @object-ui/i18n.
  • Gates: check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:control-bytes, check:unreferenced-sources — all exit 0 on the final head. check:i18n-keys confirms every call site passes exactly the arguments that value has holes for, which is the interpolation contract for the four new keys.
  • Ablation, both legs, from the committed state: restoring the pre-finding(fields): LocationField's THIRD refusal sentence (the #6715 residue arm) is still hard-coded English after the #6755 ruling keyed the other two #6888 literal turned the new file 13 failed / 13 passed. The 13 that still passed are precisely the en, provider-less and pack-value assertions — which is the point: the English pins are green in both states and could never have caught this, so only the translated assertions can. Mutation proven on disk before reading the result (injected marker count 1, keyed-call count 0, blob hash moved off the HEAD blob); restore proven by state rather than exit code (git diff HEAD empty, blob hash back to 046c63c3, marker count 0). vitest resolves @object-ui/i18n through the root config's alias to packages/i18n/src, so the source mutation is what ran.
  • Lint, measured not narrowed: eslint . --no-inline-config --format json completed over the whole repo at 0aa5812ac4384 files, the universe read from eslint's own config resolution rather than estimated. My 14 linted files: 0 errors, 3 warnings (two no-explicit-any field fixtures matching the sibling pin file's existing pattern, and one pre-existing react-hooks/set-state-in-effect that only surfaces because --no-inline-config suppresses its existing eslint-disable-next-line, in a region my diff does not touch). The repo's 94 errors are all in files this branch does not modify, and the config enables no type-aware linting, so this diff cannot move any untouched file's verdict.

Out-of-scope findings, filed unassigned rather than fixed here

Neither is touched by this PR. objectui#4752 (@object-ui/components) is untouched as required.


Generated by Claude Code

…ring arity explicitly

objectui#6715 added a THIRD refusal arm to LocationField — a half that is only
partly a number (`12abc, 34`) — after objectui#6755's ruling had already keyed
the other two. All three arms share one `<p>` and one `refusalError` state, so
that single line spoke the reader's language on two arms and English on the third.

The arity question the other two sentences never had to answer is answered here
rather than defaulted. `verb` was English grammar (`is not a number` /
`are not numbers`); a pack whose plural rules differ cannot inflect around an
English verb form passed through a hole. So the verb lives inside two SIBLING
keys picked at the call site — this repo's own plural convention
(`lookup.recordCount`/`recordCountOne` in the same defaults map), not i18next's
`_one`/`_other` suffixes, which zh/ja/ko would legitimately omit and
`all-locales-key-parity` would read as a missing key.

The English conjunction and the coordinate nouns go the same way: `latitude` and
`longitude` are keyed once each and interpolated into both arities, leaving only
the characters the person typed in holes.

English is byte-identical in both arities, so objectui#6715's own strictNumeric
pins and plugin-form's ObjectForm.locationResidue suite are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
 pin

`builtInLocales` is an `as const` object, so `(builtInLocales as any)[lang]`
threw away the one thing that makes a renamed or dropped key fail loudly: with
the access typed, `type-check` rejects it at compile time instead of leaving a
`toContain` to run against `undefined`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3187.7 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-Bfn8uX2z.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.09KB 113.98KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 243.04KB 61.36KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.29KB 13.18KB
plugin-charts (index.js) 70.35KB 19.68KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.75KB 63.50KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.46KB 14.57KB
plugin-list (index.js) 113.34KB 27.72KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.48KB 20.81KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Contract review — PASS — PR #8150 (objectui#6888), head 0aa5812ac

Reviewed-by: domain:ui @ objectui PM seat, session_01YBWFb5YgMU5dw8p2VKj16S, in-seat at opus = TIER_DEFAULT. ⛔ Not described as contract-review tier: CONTRACT_REVIEW_TIER is claude-fable-5-1 and is measured unavailable to this session (429). This review ran at opus.

Diff read by ref against origin/main: 15 files, 521 insertions / 14 deletions.

⭐ The arity answer — verified independently, ⛔ not accepted from the report

This was the dispatch's binding clause: answer it explicitly from a survey, or stop and report. The report claims a precedent. I re-measured it on origin/main rather than take it:

packages/fields/src/widgets/useFieldTranslation.ts:30   'lookup.recordCount':    '{{count}} records'
packages/fields/src/widgets/useFieldTranslation.ts:31   'lookup.recordCountOne': '1 record'
packages/plugin-list/src/ListView.tsx:656               'list.recordCountOne'
packages/plugin-detail/src/useDetailTranslation.ts:80   'detail.reactionCountOne'
packages/plugin-detail/src/ReactionPicker.tsx:55        the reason, stated in source

⇒ the X / XOne sibling pair is already in the very FIELD_DEFAULTS map this card writes into, two lines from where the new keys land. Lit control on the same corpus: grepping the locale packs for an i18next _one / _other family returns zero — so the convention is not merely present, it is exclusive.

And the methodological catch is the best thing in this round. The precedent lives in RecordPickerDialog, which is one of the four unregistered helpers, not one of the nine registered widgets. A survey scoped to "widgets" — the obvious scoping, and the one the card's own "11 widgets" framing invites — would have returned no precedent and led to a stop-and-report, or worse, to inventing _one/_other. The dev noticed that its denominator choice determined its answer, and said so. ⇒ this is the difference between a survey and a search that confirms what you expected.

The card's and triage's "11 widgets" is also correctly dismantled rather than repeated or silently replaced: 21 matching files − 7 .test. − 1 definition = 13 non-test consumers, of which 9 are registry-reachable and 4 are helpers. Controls on the same pass: createSafeTranslation returns 7 not 21 (the corpus is not matching everything) and LocationField is among the 21 (not matching nothing). My dispatch told it not to carry the 11 forward; it didn't carry my 21 forward either, which is the right response to a PM number.

① Derived judgments

Clause-②: NO — and I record the one case I actually had to think about rather than asserting it. The narrow reasons are sound: English values byte-identical (check:i18n-drift's own words, 0 en value(s) changed (4 key(s) added, 0 removed)); no parse or accept/reject movement; both new functions module-private. I independently confirmed the last one — type ResidueHalf carries no export and LocationField.tsx exports 2 symbols total, so narrowing label from string to the COORDINATE_LABELS union is genuinely module-internal.

⚠️ The case worth naming: builtInLocales is exported, and four keys per pack do widen its inferred type. I judge that outside Clause-② — the packs are translation data, not an authoring contract; no node's accept/reject behaviour moves; and every prior card in this family (#4028, #6755, #7173) added pack keys the same way without a contract carrier. ⛔ Recording it because "no exported symbol added or removed" is not by itself the same claim as "the public face does not widen", and the difference should be visible in the record rather than glossed.

Byte-identity, checked by reading rather than trusting the gate. Old: `Not saved: ${named} ${verb}. Enter plain decimals (example: 30.2741, 120.1551).` with named joined by ' and '. New en: Not saved: {{name}} "{{text}}" is not a number. … and … "{{text}}" and {{otherName}} "{{otherText}}" are not numbers. …. Substituting gives the old strings exactly, in both arities. ✅ Which is why #6715's LocationField.strictNumeric.test.tsx and plugin-form's ObjectForm.locationResidue.test.tsx pass untouched — the load-bearing evidence, and it is a measurement rather than an assertion.

The ar dual is the point of the whole exercise and it is pinned by name. ليسا رقمين (two) against ليس رقمًا (one) is exactly the form an English verb through a {{hole}} could never have produced. The card, triage and the dispatch all flagged this as the real risk; the diff answers it in the artefact rather than in prose.

coordinateName branches over string literals rather than building t(`fields.location.${label}`), with the reason in source: check:i18n-keys and check:i18n-dead-keys both read t() literals, so a template key would be invisible to the gate that proves it resolves and to the one that proves it is still used. ⭐ That is a gate-aware decision, not a stylistic one — a computed key would have passed review and silently left both gates blind.

② semver

@object-ui/fields: patch + @object-ui/i18n: patch. Correct: no behaviour moves, English byte-identical, additive keys. ⛔ Not minor — nothing breaks and nothing new is authorable. The changeset explains the defect, the arity decision and the rejected alternative, which is the house form.

③ Boundary flags — the ratchet

One ratchet moved, and I verified the arithmetic rather than accepting "three German values, three pairs". de-quote-pairing-3876.test.ts: 59 → 62, rdq still 0. From the diff, the de pack's refusedResidueOne contributes one matched „…“ pair („{{text}}“) and refusedResidue contributes two („{{text}}“, „{{otherText}}“); latitude / longitude carry no quotes at all. 59 + 3 = 62. ✅ Exact, and rdq: 0 — the load-bearing half, since a stray closer would make close === open true for the wrong reason — is unchanged. ⇒ a classified move in the file's own idiom with a per-card note, ⛔ not a number edited into agreement. Same shape as #8142's census fix, and the same standard.

The ablation's self-critique is the sharpest line in the report. Restoring the literal gave 13 failed / 13 passed, and the dev identified which 13 survived: the en, provider-less and pack-value assertions — 「The English pins are green in BOTH states and could never have caught this defect.」 ⇒ it distinguished the pins that are load-bearing from the ones that merely document, on its own work, unprompted. Mutation proven on disk before the result was read (marker 1, keyed-call 0, blob off HEAD); restore proven by state (git diff HEAD empty, blob back to 046c63c3) rather than by an exit code.

Lint measured, ⛔ not narrowed: whole repo, --no-inline-config --format json, 4384 files with the universe read from eslint's own config resolution. 0 errors on the 14 touched files; the repo's 94 errors all sit in untouched files, and with no type-aware linting configured this diff cannot move their verdict. ✅ The invariance leg is what makes that a valid scoping.

Triage's overturn condition considered and correctly not exercised — nothing in #6755's PR, tests or source freezes the tree, and LocationField.tsx carried the opposite instruction naming this card by number. ✅ objectui#4752 untouched as required.

Out-of-scope findings filed, ⛔ not folded in: #8148 (badInputMessage is a fifth hard-coded refusal, one literal shared by NumberField/CurrencyField/PercentField/GeolocationField — and ⭐ GeolocationField sits beside LocationField, so after this PR two adjacent coordinate widgets refuse in two different languages, which is this card's own defect reappearing one widget over) and #8149 (the placeholder fallback, correctly withheld because extending a refusal-sentence ruling to a placeholder is a fresh judgement). Both are right to be cards rather than riders.

Verdict

PASS.Not landing yet — CI unconverged: 32 enumerated against total_count: 32, 7 still in_progress (Lint, Test shards 1–4, Type Check, Live E2E), zero failures. ⚠️ Lint is specifically not yet readable and this seat will ⛔ not flip ready without it. The PR is behind main (which moved to c14d3a090 when #8142 landed) — a behind PR enqueues fine and the queue rebuilds.

Live E2E (informational) will fail; its signature will be checked against objectstack#16186's before it is stood down, ⛔ not assumed.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Landing — PR #8150 (objectui#6888), head 0aa5812ac, 2026-09-06T21:0xZ.

Pre-landing three:

Contract review PASS (comment 5562082036) at opus. No carrier to clear — Clause-② assessed no, with the one borderline case recorded rather than glossed (adding pack keys does widen builtInLocales' inferred type; judged outside Clause-② because the packs are data, no accept/reject behaviour moves, and #4028 / #6755 / #7173 all did the same without a carrier). Head unchanged since the review.

CI converged, enumerated 32 against total_count: 3228 success · 3 skipped · 1 failure, ⛔ zero in_progress.

Lint read explicitly and green (completed_at 20:49:12Z). ⚠️ This one mattered more than usual: the dev's own lint run was repo-wide, 4384 files with an invariance argument rather than a narrowing, so CI agreeing is a confirmation of a claim that was already strong — ⛔ not a substitute for one that was missing.

The single failure is Live E2E (informational), and its signature was read, not assumed (job 101555693813): the same no such table: sys_user reads, the same one-per-minute WARN [recipients] email 'admin@objectos.ai' lookup failed … keeping verbatim loop, the same ##[error]Process completed with exit code 1, the same informational-lane summary. Matches the break carded at objectstack#16186 (published @objectstack/plugin-auth floats @better-auth/core to 1.7.3, which dropped createLocalAccountIssuer). The red is by design per objectui#7689, whose triage ⛔ forbids reverting the pin to get green. ⛔ No re-run spent.

⚠️ Note this diff structurally cannot reach that failure: the step starts a published @objectstack/* backend and dies at readiness before any objectui code is exercised. That is an argument from mechanism, offered alongside the signature match rather than instead of it.

Ready flipped, auto-merge SQUASH enabled (⚠️ the API echoes an empty method even when it applied — verify by queue ref and parent-count, ⛔ not by that echo).

⭐ Landing control established BEFORE the queue picks it up, across all three faces this card touches, so the post-merge confirmation is a reading:

read branch origin/main
refusedResidueOne in packages/i18n/src/locales/en.ts 1 0
fields.location.refusedResidueOne in useFieldTranslation.ts 1 0
the ar dual ليسا رقمين in packages/i18n/src/locales/ar.ts 1 0
en.ts line count on main (instrument live?) 4076

⇒ every zero is a reading on a live file. ⭐ The ar row is deliberately one of the three: it is the assertion that separates the answer taken (arity in the pack value) from the one rejected (an English verb through a {{hole}}), so it is the marker worth confirming landed — ⛔ not just that some key arrived.

Fixes #6888, so the card closes on merge and this seat strips pm:dispatched and clears the assignee in the same stroke.


Generated by Claude Code

Merged via the queue into main with commit 320374d Sep 6, 2026
33 of 34 checks passed
@os-justin
os-justin deleted the claude/issue-6888-location-residue-i18n branch September 6, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(fields): LocationField's THIRD refusal sentence (the #6715 residue arm) is still hard-coded English after the #6755 ruling keyed the other two

2 participants