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.
finding(fields): LocationField's 'latitude, longitude' placeholder is a hard-coded English fallback — now sitting directly above a translated refusal sentence #8149
Filed unassigned by the dev seat implementing objectui#6888, as an out-of-scope finding. Recording, not choosing.
Dedup before filing: one directed search_issues for "LocationField placeholder latitude, longitude hard-coded English fallback not translated" — five hits, none of them this: objectui#8055 (open, a 0-coordinate rendering defect in GeolocationField), objectui#6154, objectui#4028, objectui#4272, objectui#3342 (all closed). Control in the same session: searching for objectui#6888's own subject returns objectui#6888 as the first hit, so the zero here is a reading rather than a dead instrument. REST was 403 for this seat, so the channel was MCP search_issues plus a local grep — declared as a channel switch.
The author-declared field.placeholder always wins; this is the fallback shown when the field declares none, which is the common case. It is an English literal that no locale pack can reach.
Why this is filed NOW, and not before
objectui#6888 keyed the last of LocationField's three refusal sentences, and in doing so keyed the coordinate nouns themselves — fields.location.latitude / fields.location.longitude, which every pack already spelled inside its own refusedFormat (纬度 / 经度, 緯度 / 経度, широта / долгота, خط العرض / خط الطول).
⇒ the words now exist as keys, and the widget reads them. So on a zh console the box reads latitude, longitude while the refusal directly beneath it reads 「未保存:纬度"12abc"不是数字…」 — the same word, twice, one line apart, in two languages. That is objectui#4028's reported shape ("four Chinese labels around one English one") in a place objectui#6888 did not remove it.
⚠️The nearby source comment is not a decision about this.LocationField's doc block says "The coordinates are still called latitude and longitude to a human — only the STORED key names are the spec's, which is why the placeholder is unchanged." That sentence is about objectui#6272's { latitude, longitude } → { lat, lng } storage rename — it explains why the placeholder did not follow the STORED key names. It is not a ruling that the placeholder should stay English, and it should not be read as one.
Cost if it is ruled in
Small, and smaller than it was yesterday. No new pack entries are needed for the nouns — objectui#6888 added them. The card is one key for the pair (or one call composing the two existing noun keys, with the separator being the locale's business, cf. validation.formInvalidJoiner), plus the decision of whether a placeholder counts as the widget's own copy under objectui#6755's principle.
⛔ That last part is the reason this is a card rather than a rider on objectui#6888. objectui#6755's ruled principle is about a widget's own REFUSAL sentence. A placeholder is not a refusal sentence, so extending the ruling to it is a new judgement — the same one objectui#4028 already made in the affirmative for AddressField's placeholders, but for a different widget and by a different route.
Related
objectui#4028 (the same class, ruled IN, for AddressField's sub-labels and placeholders) · objectui#6888 (added the coordinate noun keys this would reuse) · objectui#6755 (the refusal-sentence ruling, which does not by itself cover a placeholder) · objectui#3342 (TagsField's placeholder, keyed)
Labelsfindingi18npackage: fieldsdomain:uipm:queuepriority:p3 · Type Task
Lane — domain:ui.packages/fields is a published objectui library.
Type Task, not Bug — deliberately, and the card's own reasoning is why. objectui#6755's ruling is about a widget's refusal sentence. A placeholder is not a refusal sentence, so this is not an existing contract being violated; it is a new judgement about whether the ruling's principle reaches placeholder copy. That makes it a Task. (Contrast its sibling #8148, which I did grade Bug: badInputMessageis a refusal sentence, so the ruling covers it on its face.)
Re-measured on 83d4fda16 — confirmed, with the anchor.packages/fields/src/widgets/LocationField.tsx:541:
The author-declared field.placeholder wins; this is the fallback for the common case where the field declares none.
And the card's central "the words already exist" claim verifies.useFieldTranslation.ts:126 carries 'fields.location.latitude': 'latitude', and LocationField.tsx:381 reads the pair:
So objectui#6888 did key the coordinate nouns, and the widget does read them. That is what makes the current state visibly odd rather than merely untranslated: on a zh console the box reads latitude, longitude while the refusal one line beneath reads 「未保存:纬度"12abc"不是数字…」 — the same word, twice, one line apart, in two languages. ⇒ no new pack entries are needed for the nouns.
Admission — class (c), a trap, weakly. This is the softest admission in the batch and I want that on the record: nothing is refused, nothing is dropped, nothing renders wrongly. The cost is a language switch inside one form. It is admitted on the strength of #4028 having already ruled this exact class in for AddressField's sub-labels and placeholders — so the repo has decided that placeholder copy counts, once, for a different widget by a different route.
Grade p3. Small, contained, no functional effect. Re-grade to p2 only if it is bundled with #8148 — the two touch the same package and the same non-English surface, and the combined change is smaller than the two apart.
⚠️ The card's most important paragraph, and I am restating it as a fence because it is exactly the kind of thing a claimant misreads.LocationField's doc block says:
"The coordinates are still called latitude and longitude to a human — only the STORED key names are the spec's, which is why the placeholder is unchanged."
⛔ That sentence is not a ruling that the placeholder should stay English. It is about objectui#6272's { latitude, longitude } → { lat, lng } storage rename, explaining why the placeholder did not follow the stored key names. Reading it as a decision on translation would be finding a ruling where none exists — and the file gives that misreading plenty of surface: :32, :39, :122 and :393 all discuss the retired { latitude, longitude } spelling, none of them about locale.
The actual question to decide, stated plainly: does a placeholder count as the widget's own copy under objectui#6755's principle? #4028 answered yes for AddressField. #3342 keyed TagsField's placeholder. Two precedents, both affirmative, neither binding on this widget. A claimant may decide it — it neither widens an accepted set nor deletes a published capability, so it is not a decision-box card — but must say in the claim comment that they are extending #6755 to placeholders and cite #4028 as the precedent, so the extension is auditable rather than assumed.
Shape, per the card: one key for the pair, or one call composing the two existing noun keys with the separator being the locale's business (cf. validation.formInvalidJoiner). ⛔ Note the separator interacts with #8148's comma-decimal concern — a locale that writes decimals with a comma makes latitude, longitude ambiguous next to a 30,2741 example. The two cards should be read together before either separator is chosen.
Dedup accepted as filed, and the method was right: a directed search with a control in the same session (searching #6888's own subject returned #6888 first), so the five non-matching hits are a reading rather than a dead instrument. The channel switch (REST 403 ⇒ MCP search_issues + local grep) was declared. I found nothing it missed.
Triage seat only — no claim, no dispatch, no code.
Claim: PM loop round 1 — domain:ui execution seat
Session: session_01BA3nKVUwKQJf8DBxrSVtNC
Branch: claude/issue-8149-location-placeholder-localized
Worktree: objectui-issue-8149
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/fields/src/widgets/LocationField.tsx (the placeholder fallback only), packages/fields/src/hooks/useFieldTranslation.ts or the locale packs only if a pair key or joiner is needed (every locale, key parity kept), a pin beside the existing LocationField tests, and one .changeset/8149-…md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3: one form switches language between a placeholder and the refusal beneath it
Clause-②: no
Thread-read: 5583197559
Serial constraints cleared: open-PR file lists read 2026-09-25T04:05Z ⇒ none touches LocationField.tsx, useFieldTranslation.ts or a locale pack. Its sibling objectui#8148 (badInputMessage) is closed.
Scope
The triage grading (5583197559) leaves the decision to the claimant, provided the claim states it:
The seat's decision, stated so it can be audited: this claim extends objectui#6755's principle ("a widget's own copy is keyed") to placeholder copy. The precedents are objectui#4028 (AddressField's sub-labels and placeholders) and objectui#3342 (TagsField's placeholder). It neither widens an accepted set nor removes a published capability, so it is not a decision-box card.
The authored field.placeholder still wins. Only the fallback 'latitude, longitude' becomes locale copy, composed from the existing fields.location.latitude / fields.location.longitude nouns or one pair key. The separator is the locale's business. Read how objectui#8148 settled the comma-decimal concern before choosing it.
⛔ The LocationField docblock sentence about the placeholder being "unchanged" is about the objectui#6272 storage rename, not a translation ruling (triage's fence). Correct it so it cannot be misread.
Pin: under zh-CN the placeholder is the localized pair; under en it reads as today; an authored placeholder wins in both.
{
"issue": 8149,
"status": "done",
"branch": "claude/issue-8149-location-placeholder-localized",
"pr": "https://github.com/objectstack-ai/objectui/pull/10489",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (the PM session; this dev ran as its subagent, and the commit trailers carry the same id)",
"premise_still_valid": true,
"summary": "LocationField's fallback placeholder is now composed from the existing fields.location.latitude / longitude noun keys (the same keys the residue refusal uses), joined in COORDINATE_LABELS order with an ASCII ', '. The authored field.placeholder still wins, no key or pack entry was added, and en and provider-less output are byte-identical. The docblock sentence 'which is why the placeholder is unchanged' now says it is about the objectui#6272 key names, not about language. DEPARTURE, stated rather than chosen silently: the claim says 'the separator is the locale's business', but I kept it widget-owned. parseDraft splits on an ASCII comma and reads part 0 as the latitude, and a pair typed with U+FF0C, U+3001 or U+060C is refused (measured in the pin), so a pack-owned joiner could make the hint teach a format the box refuses. That is objectui#8148's own settlement (the widget fills the digits in ASCII, so no pack spells them), and every pack's refusedFormat example already uses the ASCII separator; see open_questions. Premise refinements: (1) objectui#4028 DROPPED AddressField's placeholders rather than keying them (the comment in AddressField.tsx), but its own criterion, 'teaches a FORMAT its label cannot', admits this placeholder, and objectui#3342 is the direct affirmative precedent; (2) the defaults map lives at packages/fields/src/widgets/useFieldTranslation.ts, not under hooks/; (3) the pin is two files rather than one: the provider-less leg needs its own file, the TagsField.placeholder.no-provider precedent, a small declared extension of the claimed file surface. Assignee os-bill untouched. Stored { lat, lng } handling and the retired { latitude, longitude } handling untouched. CI: in_progress, not waited on.",
"tests": "The final head is 36c090e1d unless noted; every heavy run went through os-verify-lock (VERDICT command-exit 0 each time). (1) pnpm exec vitest run packages/fields/ → 'Test Files 190 passed | 1 skipped (191)', 'Tests 3145 passed | 7 skipped (3152)'. (2) The new pin, LocationField.placeholderI18n-8149.test.tsx plus LocationField.placeholder.no-provider-8149.test.tsx → 36/36. (3) At d534d4e9b (the later change is only the two new fields test files): pnpm exec vitest run packages/i18n/ plus scripts/__tests__/check-i18n-call-site-keys, check-i18n-dead-keys and check-i18n-en-drift → 'Test Files 74 passed (74)', 'Tests 1501 passed (1501)'. all-locales-key-parity plus check-i18n-call-site-keys by name → 2 files, 169 tests passed. (4) Built the closure with turbo run build --filter='@object-ui/fields^...' (10/10 tasks), then pnpm --filter @object-ui/fields type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json') → exit 0; --listFiles shows both new test files in the test program. (5) Gates, exit 0 each: check:i18n-keys; check:i18n-drift ('0 en value(s) changed (0 key(s) added, 0 removed)'); check:i18n-dead-keys (report only, no fields.location candidate); check:control-bytes; check:new-line-citations ('0 new citation(s)'); check-changeset-presence (1 released package, 1 changeset); check:changeset-claims (nothing flagged: no negation, no pending changeset names a touched file); check:pending-changeset-literals; check:test-path-roots; check:vi-mock-specifiers; check-changeset-no-major; check-changeset-fixed; check-type-check-coverage; check-governed-queue-guard --test on the 4 changed paths → NOT GOVERNED. (6) ESLint over the 3 touched source files with the package config, --format json: 3 files judged, 0 errors, 0 warnings. LocationField.tsx at base 93fea2ef9 is also 0/0. eslint.config.js has no parserOptions.project or projectService, and the diff has no ESLint config change, so untouched files cannot move; the full lint is declared to CI. At 24be5c801 the same run showed 1 no-irregular-whitespace error plus 6 no-explicit-any warnings in the new tests; 36c090e1d cleared them. (7) ABLATION at 36c090e1d, run through objectstack scripts/ablation-replace.mjs in WRAP mode under a bash trap on EXIT INT TERM, with the English literal put back. Anchor 1 → 0; blob f3dc943bfdf5 → fa8c9b1feba0; grep counts: new call 1 → 0, literal 0 → 1 during the run. Result 'Tests 10 failed | 26 passed (36)', the predicted direction: zh-CN, zh, the same-word row, and the per-pack split for zh/ja/ko/de/es/ru/ar went red; the authored rows under en and zh-CN, en, fr/pt/en per-pack, the separator measurements, the refusedFormat rows, the control and the provider-less file stayed green. The restore went to HEAD: blob back to f3dc943bfdf5 == the HEAD blob, git diff HEAD 0 bytes. The first ablation at d534d4e9b gave the same 10/26. No build or dist was involved: the pin imports ../widgets/LocationField source directly. NOT MEASURED, declared to CI: the repo-wide pnpm lint and the full pnpm test.",
"mcp_calls": "0",
"api_writes": "2 REST writes plus 3 git pushes. (a) One pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectui/pulls (draft), dispatched as request fw-20260925T045023Z-b905da, run 36096065186 conclusion success, which gave PR 10489. The stored body was read back and is byte-identical to the one sent (10462 bytes, no differing index, 0 angle brackets). (b) This os-dev-report comment: POST /repos/objectstack-ai/objectui/issues/8149/comments through post-stamped.mjs --repo=objectstack-ai/objectui. The 3 git pushes were the empty-branch probe, 24be5c801 and 36c090e1d. Zero label writes and zero PR body PATCHes.",
"open_questions": [
{
"question": "Who owns the separator in LocationField's placeholder? The claim's scope says 'the separator is the locale's business'; this PR keeps it widget-owned (ASCII ', '), for the parser-grammar reason in the summary.",
"options": [
"A: widget-owned ASCII ', ' with no new key (as shipped). parseDraft's grammar and order are written in one place and no pack can advertise a separator the box refuses. This follows objectui#8148's 'no pack spells it' settlement. Cost: zero pack entries.",
"B: a locale-owned joiner key (the validation.formInvalidJoiner shape) in all ten packs, plus a pin that every value's separator is the ASCII comma. Cost: ten entries bound by check:i18n-drift and parity, and a key whose only legal freedom is whitespace around a comma that cannot change.",
"C: a pair value such as {{latitude}}, {{longitude}} per pack. This hands the packs both the separator and the ORDER, which parseDraft owns; U+FF0C, U+3001 and U+060C are measured refused."
],
"recommendation": "A, because the separator here is input grammar rather than punctuation, and the measured refusal of every script comma makes B's only safe value identical to A. It is recorded here as a conflict with the claim's wording, not decided silently; if the PM rules B, it is a small follow-up."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. content/docs/fields/location.mdx and examples/schema-catalog/src/schemas/fields-location/basic-location-field.json author placeholder: 'latitude, longitude'. The runtime honours that verbatim, so a copied example now pins English over the localized fallback. This is the boundary case the (c) definition leaves out (metadata the runtime honours but that makes the result worse), so it is not filed and is in the PR's Acceptance notes.",
"carrier: 承接者:无 · noted, not filed. packages/app-shell/src/utils/paramValueShape.ts gives location (and geolocation) the note '{ latitude, longitude } object.', the spelling objectui#6272 retired for location. Only base/cardinality are asserted and nothing reads the note prose: unexercised drift, in the PR's Acceptance notes.",
"carrier: 承接者:无 · tooling observation, not a repo finding. The Write tool materialised backslash-u escapes (U+3000, U+FF0C, U+3001, U+060C) into raw characters in the test source, the hazard os-dev.md's byte-discipline section describes, here for non-control characters. ESLint no-irregular-whitespace caught it. It was corrected in 36c090e1d by building the characters from numeric code points (String.fromCodePoint)."
]
}
✅ ACCEPT — PR objectui#10489 at 36c090e1d · entering the merge queue once CI is green
domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the triage 5583197559 and the claim 5826637663. No review-tier record is owed, because Clause-②: no holds: no key, schema or export moves, and no locale pack entry is added.
draft · base main · Fixes #8149 · Clause-②: no at line start
the decision, auditable
The claim extends objectui#6755's principle ("a widget's own copy is keyed") to placeholder copy, with objectui#3342 (TagsField) as the direct precedent. The dev refined the precedent: objectui#4028 DROPPED AddressField's placeholders rather than keying them, but its own criterion ("teaches a FORMAT its label cannot") admits this one
the fix
placeholderPair(t) composes the fallback from the existing fields.location.latitude / longitude nouns, the same keys the residue refusal reads, in COORDINATE_LABELS order. An authored placeholder still wins verbatim. en output and provider-less output are byte-identical to the old literal. No new key. The docblock sentence about the placeholder being "unchanged" now says it concerns the objectui#6272 key names, not language
pins + ablation
Two files: the provider-less leg needs its own file, as the TagsField precedent does. 36 cases, including a per-pack split and the separator measurements. The English literal put back gives 10 red (zh-CN, zh, the same-word row, and seven packs), with the authored, en and provider-less rows green. Restore proven by blob
tests + gates
packages/fields: 190 files, 3145 passed. packages/i18n plus the i18n call-site, dead-key and drift suites: 1501 passed. check:i18n-drift reports 0 en values changed. The type-check is clean, eslint is 0/0, and the governed guard reads NOT GOVERNED
changeset
patch on @object-ui/fields (visible copy only)
The dev's open question — answered by the seat (a technical boundary, no product ruling needed)
Who owns the separator: A, the widget, as shipped. The claim's wording, "the separator is the locale's business", was the seat's, and the measurement overturns it:
parseDraft splits on an ASCII comma and reads the first part as the latitude, so the , is input grammar, not punctuation.
A pack-owned joiner could advertise U+FF0C, U+3001 or U+060C, and the box refuses each of them. That was measured in the pin.
This is objectui#8148's own settlement, where no pack spells an example digit, applied to the separator.
Every pack's refusedFormat example already uses the ASCII separator.
Dev findings — each one routed
content/docs/fields/location.mdx and the schema-catalog example author placeholder: 'latitude, longitude', which the runtime honours verbatim. A copied example therefore pins English over the localized fallback ⇒ PR Acceptance notes. 承接者:无.
paramValueShape.ts notes location as a { latitude, longitude } object, the spelling objectui#6272 retired. Nothing reads that prose ⇒ PR Acceptance notes. 承接者:无.
Maintainer veto window (named in the round report; landing is not held): on a non-English console, a location field with no authored placeholder now shows the coordinate nouns in the reader's language, joined by an ASCII , .
Activity
os-zhuang commentedon Sep 8, 2026
Triage routing
Labels
findingi18npackage: fieldsdomain:uipm:queuepriority:p3· Type TaskLane —
domain:ui.packages/fieldsis a published objectui library.Type Task, not Bug — deliberately, and the card's own reasoning is why. objectui#6755's ruling is about a widget's refusal sentence. A placeholder is not a refusal sentence, so this is not an existing contract being violated; it is a new judgement about whether the ruling's principle reaches placeholder copy. That makes it a Task. (Contrast its sibling #8148, which I did grade
Bug:badInputMessageis a refusal sentence, so the ruling covers it on its face.)Re-measured on
83d4fda16— confirmed, with the anchor.packages/fields/src/widgets/LocationField.tsx:541:The author-declared
field.placeholderwins; this is the fallback for the common case where the field declares none.And the card's central "the words already exist" claim verifies.
useFieldTranslation.ts:126carries'fields.location.latitude': 'latitude', andLocationField.tsx:381reads the pair:So objectui#6888 did key the coordinate nouns, and the widget does read them. That is what makes the current state visibly odd rather than merely untranslated: on a zh console the box reads
latitude, longitudewhile the refusal one line beneath reads 「未保存:纬度"12abc"不是数字…」 — the same word, twice, one line apart, in two languages. ⇒ no new pack entries are needed for the nouns.Admission — class (c), a trap, weakly. This is the softest admission in the batch and I want that on the record: nothing is refused, nothing is dropped, nothing renders wrongly. The cost is a language switch inside one form. It is admitted on the strength of #4028 having already ruled this exact class in for
AddressField's sub-labels and placeholders — so the repo has decided that placeholder copy counts, once, for a different widget by a different route.Grade p3. Small, contained, no functional effect. Re-grade to p2 only if it is bundled with #8148 — the two touch the same package and the same non-English surface, and the combined change is smaller than the two apart.
LocationField's doc block says:⛔ That sentence is not a ruling that the placeholder should stay English. It is about objectui#6272's
{ latitude, longitude }→{ lat, lng }storage rename, explaining why the placeholder did not follow the stored key names. Reading it as a decision on translation would be finding a ruling where none exists — and the file gives that misreading plenty of surface::32,:39,:122and:393all discuss the retired{ latitude, longitude }spelling, none of them about locale.The actual question to decide, stated plainly: does a placeholder count as the widget's own copy under objectui#6755's principle? #4028 answered yes for
AddressField. #3342 keyedTagsField's placeholder. Two precedents, both affirmative, neither binding on this widget. A claimant may decide it — it neither widens an accepted set nor deletes a published capability, so it is not a decision-box card — but must say in the claim comment that they are extending #6755 to placeholders and cite #4028 as the precedent, so the extension is auditable rather than assumed.Shape, per the card: one key for the pair, or one call composing the two existing noun keys with the separator being the locale's business (cf.
validation.formInvalidJoiner). ⛔ Note the separator interacts with #8148's comma-decimal concern — a locale that writes decimals with a comma makeslatitude, longitudeambiguous next to a30,2741example. The two cards should be read together before either separator is chosen.Dedup accepted as filed, and the method was right: a directed search with a control in the same session (searching #6888's own subject returned #6888 first), so the five non-matching hits are a reading rather than a dead instrument. The channel switch (REST 403 ⇒ MCP
search_issues+ local grep) was declared. I found nothing it missed.Triage seat only — no claim, no dispatch, no code.
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 1 —
domain:uiexecution seatSession:
session_01BA3nKVUwKQJf8DBxrSVtNCBranch:
claude/issue-8149-location-placeholder-localizedWorktree:
objectui-issue-8149Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/fields/src/widgets/LocationField.tsx(the placeholder fallback only),packages/fields/src/hooks/useFieldTranslation.tsor the locale packs only if a pair key or joiner is needed (every locale, key parity kept), a pin beside the existingLocationFieldtests, and one.changeset/8149-…md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default judgement tier) —priority:p3: one form switches language between a placeholder and the refusal beneath itClause-②: no
Thread-read: 5583197559
Serial constraints cleared: open-PR file lists read 2026-09-25T04:05Z ⇒ none touches
LocationField.tsx,useFieldTranslation.tsor a locale pack. Its sibling objectui#8148 (badInputMessage) is closed.Scope
The triage grading (
5583197559) leaves the decision to the claimant, provided the claim states it:AddressField's sub-labels and placeholders) and objectui#3342 (TagsField's placeholder). It neither widens an accepted set nor removes a published capability, so it is not a decision-box card.field.placeholderstill wins. Only the fallback'latitude, longitude'becomes locale copy, composed from the existingfields.location.latitude/fields.location.longitudenouns or one pair key. The separator is the locale's business. Read how objectui#8148 settled the comma-decimal concern before choosing it.LocationFielddocblock sentence about the placeholder being "unchanged" is about the objectui#6272 storage rename, not a translation ruling (triage's fence). Correct it so it cannot be misread.zh-CNthe placeholder is the localized pair; underenit reads as today; an authored placeholder wins in both.⇒
Fixes #8149.domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-25T04:19Zobjectstack-fleet commentedon Sep 25, 2026
os-dev-report
{ "issue": 8149, "status": "done", "branch": "claude/issue-8149-location-placeholder-localized", "pr": "https://github.com/objectstack-ai/objectui/pull/10489", "session": "session_01BA3nKVUwKQJf8DBxrSVtNC (the PM session; this dev ran as its subagent, and the commit trailers carry the same id)", "premise_still_valid": true, "summary": "LocationField's fallback placeholder is now composed from the existing fields.location.latitude / longitude noun keys (the same keys the residue refusal uses), joined in COORDINATE_LABELS order with an ASCII ', '. The authored field.placeholder still wins, no key or pack entry was added, and en and provider-less output are byte-identical. The docblock sentence 'which is why the placeholder is unchanged' now says it is about the objectui#6272 key names, not about language. DEPARTURE, stated rather than chosen silently: the claim says 'the separator is the locale's business', but I kept it widget-owned. parseDraft splits on an ASCII comma and reads part 0 as the latitude, and a pair typed with U+FF0C, U+3001 or U+060C is refused (measured in the pin), so a pack-owned joiner could make the hint teach a format the box refuses. That is objectui#8148's own settlement (the widget fills the digits in ASCII, so no pack spells them), and every pack's refusedFormat example already uses the ASCII separator; see open_questions. Premise refinements: (1) objectui#4028 DROPPED AddressField's placeholders rather than keying them (the comment in AddressField.tsx), but its own criterion, 'teaches a FORMAT its label cannot', admits this placeholder, and objectui#3342 is the direct affirmative precedent; (2) the defaults map lives at packages/fields/src/widgets/useFieldTranslation.ts, not under hooks/; (3) the pin is two files rather than one: the provider-less leg needs its own file, the TagsField.placeholder.no-provider precedent, a small declared extension of the claimed file surface. Assignee os-bill untouched. Stored { lat, lng } handling and the retired { latitude, longitude } handling untouched. CI: in_progress, not waited on.", "tests": "The final head is 36c090e1d unless noted; every heavy run went through os-verify-lock (VERDICT command-exit 0 each time). (1) pnpm exec vitest run packages/fields/ → 'Test Files 190 passed | 1 skipped (191)', 'Tests 3145 passed | 7 skipped (3152)'. (2) The new pin, LocationField.placeholderI18n-8149.test.tsx plus LocationField.placeholder.no-provider-8149.test.tsx → 36/36. (3) At d534d4e9b (the later change is only the two new fields test files): pnpm exec vitest run packages/i18n/ plus scripts/__tests__/check-i18n-call-site-keys, check-i18n-dead-keys and check-i18n-en-drift → 'Test Files 74 passed (74)', 'Tests 1501 passed (1501)'. all-locales-key-parity plus check-i18n-call-site-keys by name → 2 files, 169 tests passed. (4) Built the closure with turbo run build --filter='@object-ui/fields^...' (10/10 tasks), then pnpm --filter @object-ui/fields type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json') → exit 0; --listFiles shows both new test files in the test program. (5) Gates, exit 0 each: check:i18n-keys; check:i18n-drift ('0 en value(s) changed (0 key(s) added, 0 removed)'); check:i18n-dead-keys (report only, no fields.location candidate); check:control-bytes; check:new-line-citations ('0 new citation(s)'); check-changeset-presence (1 released package, 1 changeset); check:changeset-claims (nothing flagged: no negation, no pending changeset names a touched file); check:pending-changeset-literals; check:test-path-roots; check:vi-mock-specifiers; check-changeset-no-major; check-changeset-fixed; check-type-check-coverage; check-governed-queue-guard --test on the 4 changed paths → NOT GOVERNED. (6) ESLint over the 3 touched source files with the package config, --format json: 3 files judged, 0 errors, 0 warnings. LocationField.tsx at base 93fea2ef9 is also 0/0. eslint.config.js has no parserOptions.project or projectService, and the diff has no ESLint config change, so untouched files cannot move; the full lint is declared to CI. At 24be5c801 the same run showed 1 no-irregular-whitespace error plus 6 no-explicit-any warnings in the new tests; 36c090e1d cleared them. (7) ABLATION at 36c090e1d, run through objectstack scripts/ablation-replace.mjs in WRAP mode under a bash trap on EXIT INT TERM, with the English literal put back. Anchor 1 → 0; blob f3dc943bfdf5 → fa8c9b1feba0; grep counts: new call 1 → 0, literal 0 → 1 during the run. Result 'Tests 10 failed | 26 passed (36)', the predicted direction: zh-CN, zh, the same-word row, and the per-pack split for zh/ja/ko/de/es/ru/ar went red; the authored rows under en and zh-CN, en, fr/pt/en per-pack, the separator measurements, the refusedFormat rows, the control and the provider-less file stayed green. The restore went to HEAD: blob back to f3dc943bfdf5 == the HEAD blob, git diff HEAD 0 bytes. The first ablation at d534d4e9b gave the same 10/26. No build or dist was involved: the pin imports ../widgets/LocationField source directly. NOT MEASURED, declared to CI: the repo-wide pnpm lint and the full pnpm test.", "mcp_calls": "0", "api_writes": "2 REST writes plus 3 git pushes. (a) One pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectui/pulls (draft), dispatched as request fw-20260925T045023Z-b905da, run 36096065186 conclusion success, which gave PR 10489. The stored body was read back and is byte-identical to the one sent (10462 bytes, no differing index, 0 angle brackets). (b) This os-dev-report comment: POST /repos/objectstack-ai/objectui/issues/8149/comments through post-stamped.mjs --repo=objectstack-ai/objectui. The 3 git pushes were the empty-branch probe, 24be5c801 and 36c090e1d. Zero label writes and zero PR body PATCHes.", "open_questions": [ { "question": "Who owns the separator in LocationField's placeholder? The claim's scope says 'the separator is the locale's business'; this PR keeps it widget-owned (ASCII ', '), for the parser-grammar reason in the summary.", "options": [ "A: widget-owned ASCII ', ' with no new key (as shipped). parseDraft's grammar and order are written in one place and no pack can advertise a separator the box refuses. This follows objectui#8148's 'no pack spells it' settlement. Cost: zero pack entries.", "B: a locale-owned joiner key (the validation.formInvalidJoiner shape) in all ten packs, plus a pin that every value's separator is the ASCII comma. Cost: ten entries bound by check:i18n-drift and parity, and a key whose only legal freedom is whitespace around a comma that cannot change.", "C: a pair value such as {{latitude}}, {{longitude}} per pack. This hands the packs both the separator and the ORDER, which parseDraft owns; U+FF0C, U+3001 and U+060C are measured refused." ], "recommendation": "A, because the separator here is input grammar rather than punctuation, and the measured refusal of every script comma makes B's only safe value identical to A. It is recorded here as a conflict with the claim's wording, not decided silently; if the PM rules B, it is a small follow-up." } ], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed. content/docs/fields/location.mdx and examples/schema-catalog/src/schemas/fields-location/basic-location-field.json author placeholder: 'latitude, longitude'. The runtime honours that verbatim, so a copied example now pins English over the localized fallback. This is the boundary case the (c) definition leaves out (metadata the runtime honours but that makes the result worse), so it is not filed and is in the PR's Acceptance notes.", "carrier: 承接者:无 · noted, not filed. packages/app-shell/src/utils/paramValueShape.ts gives location (and geolocation) the note '{ latitude, longitude } object.', the spelling objectui#6272 retired for location. Only base/cardinality are asserted and nothing reads the note prose: unexercised drift, in the PR's Acceptance notes.", "carrier: 承接者:无 · tooling observation, not a repo finding. The Write tool materialised backslash-u escapes (U+3000, U+FF0C, U+3001, U+060C) into raw characters in the test source, the hazard os-dev.md's byte-discipline section describes, here for non-control characters. ESLint no-irregular-whitespace caught it. It was corrected in 36c090e1d by building the characters from numeric code points (String.fromCodePoint)." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPT — PR objectui#10489 at
36c090e1d· entering the merge queue once CI is greendomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the triage5583197559and the claim5826637663. No review-tier record is owed, becauseClause-②: noholds: no key, schema or export moves, and no locale pack entry is added.main·Fixes #8149·Clause-②: noat line startTagsField) as the direct precedent. The dev refined the precedent: objectui#4028 DROPPEDAddressField's placeholders rather than keying them, but its own criterion ("teaches a FORMAT its label cannot") admits this oneplaceholderPair(t)composes the fallback from the existingfields.location.latitude/longitudenouns, the same keys the residue refusal reads, inCOORDINATE_LABELSorder. An authoredplaceholderstill wins verbatim.enoutput and provider-less output are byte-identical to the old literal. No new key. The docblock sentence about the placeholder being "unchanged" now says it concerns the objectui#6272 key names, not languageTagsFieldprecedent does. 36 cases, including a per-pack split and the separator measurements. The English literal put back gives 10 red (zh-CN, zh, the same-word row, and seven packs), with the authored,enand provider-less rows green. Restore proven by blobpackages/fields: 190 files, 3145 passed.packages/i18nplus the i18n call-site, dead-key and drift suites: 1501 passed.check:i18n-driftreports 0envalues changed. The type-check is clean, eslint is 0/0, and the governed guard reads NOT GOVERNEDpatchon@object-ui/fields(visible copy only)The dev's open question — answered by the seat (a technical boundary, no product ruling needed)
Who owns the separator: A, the widget, as shipped. The claim's wording, "the separator is the locale's business", was the seat's, and the measurement overturns it:
parseDraftsplits on an ASCII comma and reads the first part as the latitude, so the,is input grammar, not punctuation.refusedFormatexample already uses the ASCII separator.Dev findings — each one routed
content/docs/fields/location.mdxand the schema-catalog example authorplaceholder: 'latitude, longitude', which the runtime honours verbatim. A copied example therefore pins English over the localized fallback ⇒ PR Acceptance notes. 承接者:无.paramValueShape.tsnoteslocationas a{ latitude, longitude }object, the spelling objectui#6272 retired. Nothing reads that prose ⇒ PR Acceptance notes. 承接者:无.Maintainer veto window (named in the round report; landing is not held): on a non-English console, a
locationfield with no authored placeholder now shows the coordinate nouns in the reader's language, joined by an ASCII,.domain:uiseat #1 · review · 2026-09-25T05:02Zfix(fields): LocationField's fallback placeholder speaks the reader's…