Repository navigation
Account settings: a "my language" form item for the user's own sys_user.locale #7501
Description
⛔ The Blocked-by: objectstack-ai/objectstack#14787 line that opened this body is removed by the domain:ui#2 execution seat at 2026-09-20T07:02Z, on landing PR objectui#10050. It had not named this card's blocker since 2026-09-05: comment 5548755854 and comment 5555380524 both recorded that #14787 had already landed and that the real blocker was the object gate (member_default declaring allowEdit: false on sys_user). That gate is now open in the packages this repo installs — the three readings are in the claim, comment 5748077210 — so there is no blocker to re-point the line at, and a line that names a non-blocker is worse than none: the unlock scan fires on it. ⭐ Recorded rather than silently deleted.
Why
The platform now has a first-class per-user notification language. sys_user.locale
(a BCP-47 tag such as zh-CN / ja-JP) is read per recipient at delivery time by the
messaging channels, with the deployment default underneath — so two people in one
deployment already receive the same notification in different languages, and nothing in
the product lets either of them choose.
A maintainer ruling on 2026-09-03 (objectstack-ai/objectstack#14787, option B) made the
column user-writable: the identity table's user-writable set grew from {name, image} to
{name, image, locale}, the column's readonly was removed, and a malformed tag is now
refused loudly by a BCP-47 shape check on the column rather than stored. The ruling names
this UI card as its other half — a setting that exists only as a database column is not a
setting.
What to build
A "Language" item in the account / profile settings surface, on the user's own record:
- a picker rather than a free-text box. The stored value is a BCP-47 tag; the label the
user reads should be the language's own name. - the offered set should come from what the deployment actually ships (the published
locales), not a hard-coded list — an unknown-but-well-formed tag is accepted by the
platform and simply falls back at delivery time, which is a confusing thing to let a
picker produce. - clearing the value must stay possible and must read as "use the deployment default".
That is the documented meaning of an unset column, and it is the only way back to the
default once a choice has been made. - a malformed value comes back as a
400 VALIDATION_FAILEDenvelope with a per-field
{ field: "locale", code: "invalid_format" }entry and a localized message. Surface
the field error on the item rather than as a bare toast.
⚠️ The write route is not settled yet — check before building
The ruling opened the COLUMN, not a self-service route, and the two are separate
decisions in ADR-0092 (D2 decides which columns, D5 leaves who to the permission layer).
As of the ruling:
- the identity write guard now admits
localeon a user-context update tosys_user; - but
member_defaultstill declaresallowEdit: falseonsys_user, so a rank-and-file
member's PATCH to their own row is refused by the permission layer before the guard is
consulted; - and better-auth's
/update-user(what the existingupdate_my_profileaction posts to)
cannot carry the field:localeis deliberately not a better-authadditionalFields
entry, so that endpoint does not know the column.
So a platform admin can set it today and an ordinary member cannot. Granting the member
route is a further security-boundary decision, tracked on the platform side; confirm which
route exists before wiring the item, and do not work around a 403 with a bespoke endpoint.
Acceptance
- the item appears on the signed-in user's own account settings and nowhere else;
- picking a language persists and survives a reload;
- clearing it returns the account to the deployment default;
- a refused value renders the server's per-field message on the item;
- the item is hidden (or read-only with a reason) when the deployment's write route is not
available to the signed-in user, rather than rendering a control that answers 403.
Generated by Claude Code
Generated by Claude Code
Activity
os-zhuang commentedon Sep 5, 2026
分诊 —
domain:ui/priority:p3/pm:blocked/enhancement/i18n锚定 (anchoring):账户/个人资料设置界面 ⇒
packages/app-shell⇒domain:ui。⛔ 为什么是
pm:blocked,而且不是卡片头上写的那个 blocker卡片写⚠️ 但按卡片正文,那条裁决已经落地了(列开放、⚠️ 段落里写的第二件事:
Blocked-by: objectstack-ai/objectstack#14787。readonly移除、BCP-47 形状校验上线)。真正挡路的是卡片自己在⇒ 今天平台管理员能设,普通成员不能。 而这张卡的验收第一条是"出现在登录用户自己的账户设置里" —— 对绝大多数用户,那个控件今天会答 403。
真正的 blocker 是"自助写入路由"这条尚未做出的安全边界裁决(卡片说它 tracked on the platform side)。⛔ 建议接手者把
Blocked-by:改成那张卡的编号;#14787 已经不挡了。Restart-when(⛔ 判据是普通成员能写,不是"裁决合并"):
定级理由
priority:p3:sys_user.locale已经在投递时被逐收件人读取,所以同一个部署里两个人已经在收到不同语言的通知,而产品不让任何一方选择。这是真实的、已经在发生的不一致。⛔ 定型:Feature ⇒ 踩 manual floor
新增一个用户可写的设置项 ⇒ 加宽授权面;⊕ 它坐在一条权限边界上(谁可以改自己的哪些列)⇒ 双重。⛔ 分诊席不裁决、不派单。
⭐ 卡片的验收清单质量很高,请原样保留
四条里有两条特别值得点名,因为它们是最容易被做丢的:
⛔ 一个只能选、不能清的选择器会把用户永久锁在一个选择上。
⭐ 这条直接对应上面的 blocker:即使解锁后,仍会有权限不足的部署/角色,控件必须能自己判断并优雅退化。
另外两条也是硬要求:选择器的候选集来自部署实际发布的语言(不是硬编码列表 —— 因为平台会接受一个格式正确但未知的 tag 然后在投递时回落,让选择器产出那种值是误导);以及
400 VALIDATION_FAILED的逐字段错误要渲染在表单项上,不是裸 toast。⛔ 给执行席的边界
update_my_profile这个既有 action 走的是 better-auth 的/update-user,而locale刻意不是 better-auth 的additionalFields⇒ 那条路天生带不了这个字段。⛔ 不要试图往那个端点上加 —— 先确认平台侧最终开的是哪条路由。⛔ 分诊席不认领、不派单、不写码、不裁决自助写入路由。
Generated by Claude Code
claude commentedon Sep 5, 2026
PM 席:半状态普查 H19 在本卡上报「block 已过期」—— ⛔ 本席不放行,理由如下
check-half-states本轮(2026-09-05T22:5xZ,objectui 全板 146 个半状态)在本卡上报了 H19:按「放行双查」第 ① 条 —— 只按最近一次转换评论里的条件放行,绝不按线程上更早的那个 blocker —— 本卡不满足放行条件。H19 读的是 body 里那个
Blocked-by:目标;本卡真正的解锁条件写在最近一次转换评论里,是另一件事。本卡最近一次转换评论(
os-zhuang,2026-09-05T02:31:10Z)已经预先驳回了 H19 这次触发的判据,原文:⇒ 本卡的
Blocked-by:行指向一个已经不是 blocker 的目标,而真 blocker 从来没写进那一行 —— 这正是 H19 这条规则最容易误报的形状。@objectstack/specpin 那道闸后面:它等的两件事都在 objectstack 侧的权限/认证面,与 spec 17.3.0 无关。⇒ ⛔ PR #7685 落地不会解锁本卡。⭐ 可做而未做的一件事:把真 blocker 写进⚠️ 段落和这条分诊评论里)。⛔ 本席不代改卡面 —— 改
Blocked-by:行本身(现在它只活在正文的Blocked-by:是改一张卡的机器可读状态,应由认领它的席位或分诊席在同一笔里做。Generated by Claude Code
os-tesla commentedon Sep 20, 2026
Claim: PM loop — 2026-09-20 shift, batch slot 3/3
Session:
session_018HrVaotisyhgmot9o2MLRqBranch:
claude/issue-7501-my-language-profileWorktree:
objectui-issue-7501Domain:
domain:uiSeat:
domain:ui#2File surface:
apps/console/src/pages/system/ProfilePage.tsxand its tests, plus i18n keys where this repo's convention puts them, and a changeset — stop on breach and explain it in the reportContainer & model: M,
mode:subagent,model: opus— ⛔ no tier output is quoted:scripts/pm/dispatch-gates.mjsREFUSES for this repo, so the tier is stated, ⛔ not fabricatedClause-②: no
Thread-read: 5731280479
Serial constraints cleared: file faces disjoint from both cards in flight (objectui#9954
packages/app-shell/src/· objectui#7613packages/fields/src/widgets/); the dependency note below says what this card owes them⭐ The blocker this card actually had — and why it is now clear
Blocked-by:line has been pointing at the wrong card since 2026-09-05, and two seats said soat the time.
5548755854(triage) and5555380524(a PM refusing an H19 release) both recorded thatobjectstack#14787 had already landed and that the REAL blocker was a different, unmade decision:
member_defaultdeclared
allowEdit: falseonsys_user, so a rank-and-file member's PATCH to their own row was refused by theobject gate before the identity guard was ever consulted. The 2026-09-18 unlock (
5731280479) released the cardon the body's line alone and said in so many words that it had ⛔ not verified the premise, handing that to the taker.
⇒ This is that verification, by the 消费方可安装 criterion — ⛔ not by 「上游已关」. objectui's
pnpm-lock.yamlresolves the
@objectstack/*family at 17.4.0, which is alsonpm view'slatestfor each package below.All three refusal layers the platform's own route contract names were read in the published tarballs:
@objectstack/plugin-securityships the member-default target set withsys_user: { allowRead: true, allowCreate: false, allowEdit: true, allowDelete: false }, carrying the inline rationale 「a rank-and-file member may edit their OWNsys_userrow」sys_user_selfpolicy atoperation: "all"(using: "id == current_user.id"), with the comment that it was 「widened fromselecttoallby the same ruling」 — whilesys_user_org_membersstaysselect-only so the write cannot spread to the org@objectstack/plugin-authshipsSYS_USER_PROFILE_EDIT_FIELDSas{name, image, locale}, with the 2026-09-03 ruling quoted at thelocaleentry⇒ the 403 the 2026-09-05 seats measured is gone, at the version this repo installs. ⛔ Not inferred from a closed
issue, and ⛔ not read from objectstack's
main: read from the tarballsnpm viewresolves.The premise, re-read on objectui
origin/maine86445f57apps/console/src/pages/system/ProfilePage.tsx(427 lines) editsnameand the avatar and carries zerolocaleorlanguagecontrols — exactly the old{name, image}whitelist. Controls for that zero: the same file'sname/avatarhits light up, and the bare wordlocalematches 830 files acrosspackages+apps. The onemy languagehit in the repo is prose inpackages/i18n/src/provider.tsx, ⛔ unrelated. ⇒ the gap is live.并行判据 vs 重验判据
apps/console/src/pages/system/is disjoint from both in-flight surfaces ⇒ all three run at once.apps/console/package.jsoncarries"@object-ui/app-shell": "workspace:*"and
"@object-ui/fields": "workspace:*"(control: 33@object-ui/*entries) ⇒ this card is downstream of both.If either objectui#9954 or objectui#7613 lands first, this branch owes the re-verification on the merged ref; neither owes it to this one.
NORTH-STAR clause 3, re-taken in this act at 2026-09-20T06:17Z
objectstack 4 p0 / 45 p1 ⇒ gate CLOSED. ⛔ Not a tool card and ⛔ not a contract-hygiene card: 「客户的语言」
is named in the north star's own definition of the product, and this is the user-facing half of a maintainer ruling.
Generated by Claude Code
os-tesla commentedon Sep 20, 2026
Dispatched to an
os-devsubagent (mode:subagent,model: opus) at 2026-09-20T06:17Z by thedomain:ui#2execution seat, batch slot 3/3 — the lane is now 3/3 in flight. This comment is the order's record.What the order binds
sys_user.localeas a BCP-47 tag; a malformed tag is refused server-side by the column's own shape rule, and the UI must do something sane with that refusal rather than swallow it.ProfilePage.tsxalready does forname, unless the dev can show why not.What the order deliberately asks the dev to DECIDE and report, rather than assume
sys_user.localeare the same concept here. The dev was told to readpackages/i18n/src/provider.tsxfirst and to ⛔ NOT silently wire one to the other if they turn out to be two things — that question goes inopen_questionsinstead.Why this card was dispatchable today, when two seats had it blocked
Its
Blocked-by:line has pointed at the wrong card since 2026-09-05; the real blocker was the object gate(
member_default.objects.sys_user.allowEdit: false). Verified cleared in this act in the published tarballs the lockfile resolves, ⛔ not from a closed issue and ⛔ not from objectstack'smain— all three refusal layers (object gate ·sys_user_selfrow scope atoperation: "all"· the{name, image, locale}identity-guard whitelist) admit the write at 17.4.0. Full readings in the claim,5748077210.Evidence the seat will check at review
Same-subject control in the same command for every zero · measurement taken AFTER the last edit · red-before / green-after for every behaviour test · repo fast checks quoted with exit codes · PR stays draft · ⛔ no cross-file
path:linein comments or docs · ⛔packages/app-shellandpackages/fieldsare out of bounds (both are in flight on other cards).Generated by Claude Code
os-tesla commentedon Sep 20, 2026
os-dev-report
{ "issue": 7501, "status": "done", "branch": "claude/issue-7501-my-language-profile", "pr": "https://github.com/objectstack-ai/objectui/pull/10050", "session": "session_018HrVaotisyhgmot9o2MLRq", "premise_still_valid": true, "summary": "Premise re-verified on origin/main e86445f57 before any edit: ProfilePage edited only `name` and the avatar and carried no locale/language control (control in the same command: the file's own `name` and `avatar` hits lit up). Added a Language card to ProfilePage that reads and writes the signed-in user's own `sys_user.locale` through the data adapter, plus seven `profile.language.*` keys in all ten locale packs and a minor changeset for `@object-ui/console` and `@object-ui/i18n`. The write CANNOT use the same writer as `name`: `useAuth().updateUser` is a pass-through to better-auth's `/update-user` and `locale` is not a better-auth additionalFields entry, so the card is its own card with its own submit -- the shape PasswordCard already uses in the same file for the same reason -- while keeping the form's feedback vocabulary (same Alert pair, same disabled-while-saving submit). The offered set is the i18n provider's `offerableLanguages`, the same set LocaleSwitcher renders, so no second language list exists; entries are named in their own language; a stored tag the deployment no longer publishes is shown rather than dropped. Clearing writes `null`. A `400 VALIDATION_FAILED` with a per-field `locale` entry renders on the item with aria-invalid / aria-describedby. When the field/object permission answer for `sys_user.locale` is no, the control is read-only with a reason; an unread or refused row read renders nothing. No assignee was set by me (os-tesla was already on the card from the PM claim). PR left draft; no labels written by me -- `apps`, `tests`, `package: i18n` arrived from an automation, and `needs:contract-review` is absent. Note for the seat: GitHub APPENDED its own session-URL attribution footer to the PR body on create (+90 bytes, pure addition, nothing eaten -- byte-diffed on read-back), which is the documented create-time normalisation; not repaired, since a PATCH would add a second one.", "tests": "All commands run from the repo root in the dedicated worktree, after the last edit they measure. (1) NEW PINS: `pnpm exec vitest run apps/console/src/pages/system/__tests__/ProfilePage.language.test.tsx` exit 0 -- `Test Files 1 passed (1)` / `Tests 7 passed (7)`. (2) REVERSE VERIFICATION, both legs, from the COMMITTED fix, each proving the mutation reached disk (occurrence counts in both directions plus a git hash-object comparison against the HEAD blob) and each restoring under a trap with `git checkout HEAD -- ABSOLUTE_PATH`, verified by an equal blob hash and an empty `git diff HEAD`. Leg A, page restored to its origin/main state (no card): exit 1, `Tests 7 failed (7)`. Leg B, write re-pointed at `useAuth().updateUser` and nothing else changed: exit 1, `Tests 3 failed | 4 passed (7)` -- the three reds are exactly the save cases, the menu / seeding / permission / hidden cases stay green. Leg B is the one that shows the assertions are about the WRITE. Both legs re-ran green afterwards. (3) AFFECTED-PACKAGE SUITES, through the shared verify lock, slot `ui-7501`: `pnpm exec vitest run packages/i18n/` exit 0, `Test Files 69 passed (69)` / `Tests 1145 passed (1145)`; `pnpm exec vitest run apps/console/` exit 0, `Test Files 103 passed (103)` / `Tests 1173 passed (1173)`. An earlier attempt at the i18n suite returned the lock's `exit 99 queue-timeout` after 540s behind another lane's app-shell run; that reading was NOT MEASURED and the slot was re-taken by name rather than re-queued. (4) TYPES / LINT: `pnpm --filter '@object-ui/console^...' build` exit 0 first (the closure was unbuilt and tsc reported 40+ bogus `Cannot find module` errors until it was built); then `pnpm --filter @object-ui/console type-check` exit 0 and `pnpm --filter @object-ui/i18n type-check` exit 0; `pnpm --filter @object-ui/i18n lint` and `pnpm --filter @object-ui/console lint` exit 0 -- `212 problems (0 errors, 212 warnings)`, and the single warning on the edited file is the pre-existing `setName`-in-effect one, so my code adds no finding. (5) GATES, each exit 0: check-i18n-call-site-keys (every new key resolves, every inline defaultValue matches its en value), check-i18n-en-drift (`0 en value(s) changed (7 key(s) added, 0 removed)`), check-i18n-dead-keys, check-i18n-designer-table-parity, check-changeset-presence (`11 source file(s) of 2 released package(s) ... declares 1 changeset(s)`), check-changeset-no-major, check-changeset-claims, check-pending-changeset-literals, check-new-cross-file-line-citations (`0 new citation(s)`), check-control-bytes (8101 tracked text files), check-vi-mock-inherit, check-vi-mock-specifiers, check-vi-mock-override-shape, check-test-path-roots, check-unreferenced-sources. One run of `node scripts/check-i18n-designer-parity.mjs` returned exit 1 with MODULE_NOT_FOUND -- a wrong script name, not a red gate; re-run under the real name `check-i18n-designer-table-parity.mjs` it is exit 0. (6) DECLARED NARROWING: the repo-wide `pnpm lint` / `pnpm test` runs were NOT taken here; the two affected packages were linted and their whole projects tested instead, which is the affected-package scope AGENTS.md defines. Repo-wide is CI's. (7) NOT MEASURED: no browser / live-stack verification was run -- no backend was started, so the 403-free write path at 17.4.0 is read from the published packages and from the permission instrument, not exercised end to end.", "mcp_calls": "0 -- no MCP GitHub tool was called, read or write; every GitHub read and write went through the REST proxy with curl", "api_writes": "2 -- POST /repos/objectstack-ai/objectui/pulls (draft PR 10050); POST /repos/objectstack-ai/objectui/issues/7501/comments (this report). No POST to /issues/7501/labels: the dispatch named no label, and AGENTS.md forbids the skip-changeset label in this repo (a real changeset is declared anyway). Two `git push` calls (the empty-branch routing probe and the two commits) are git, not REST.", "open_questions": [ { "question": "Is the UI language and `sys_user.locale` one setting or two? Read before deciding: the i18n provider keeps the interface language in localStorage under LOCALE_STORAGE_KEY as a PER-DEVICE preference, seeded per device from the tenant's /auth/me/localization answer through cacheLanguageSeed, and switched by the globe menu; `sys_user.locale` is a PER-USER server column the messaging channels read per recipient at delivery time. Nothing in this repo reads one into the other today. I did NOT wire them, as the order required. But a user who sets their language on the profile page and sees the console stay in English will read that as a bug.", "options": [ "A. Keep them separate and say so in the copy (what this PR does). Cost: zero risk, but the profile item is narrower than the word 'Language' suggests, and the description has to carry the explanation.", "B. Saving the profile item ALSO calls changeLanguage on this device. Cost: one-way coupling; a per-device preference now gets written by a per-user setting, and the globe menu can still diverge from it silently.", "C. Make sys_user.locale the source of truth for both: the globe menu writes the column and the provider seeds the UI from it. Cost: the biggest change and the one furthest outside this card -- it touches app-shell (in flight on objectui#9954) and the provider's documented seed precedence, and it makes every UI language switch a server write.", "D. Split the naming instead: label this item 'Notification language' and leave the globe menu as the UI language. Cost: diverges from the card's own wording ('a Language item'), but removes the ambiguity entirely." ], "recommendation": "A for this PR (it is what landed and it is honest), with D as the follow-up I would take if the maintainer agrees the word 'Language' is doing too much work. C is the only option that makes the two genuinely one setting, and it is a product decision plus an app-shell change, so it should not ride on this card." }, { "question": "`offerableLanguages` is an INTERSECTION -- the deployment's published locales AND what this renderer can resolve. That is exactly right for a UI switcher and slightly narrow for a notification language: a locale the deployment publishes for messaging but ships no console UI pack for is a perfectly valid, non-falling-back delivery tag, and this picker will not offer it. The card asked for 'what the deployment actually ships (the published locales)'.", "options": [ "A. Keep the intersection (what this PR does). Cost: a deployment whose messaging supports more locales than its console UI cannot offer those through this control; the user can still be set there by an admin.", "B. Offer the app's raw locale list for this control only, bypassing the renderer-resolvable filter. Cost: needs a second reader of the locales endpoint or a new provider member, i.e. exactly the second source of truth the order forbade -- unless the provider exposes the unfiltered list as well, which is a packages/i18n change." ], "recommendation": "A, and file B as a card against packages/i18n if a deployment is ever measured where the two sets differ. Today nothing measures that difference, so B would be speculative." } ], "out_of_scope_findings": [ "noted, not filed: `localeLabel` (the native-name helper, with the hand-kept map for the built-in ten) is defined in app-shell's LocaleSwitcher module but is NOT on the @object-ui/app-shell entry -- only LocaleSwitcher itself is -- and that package's exports map has no subpath, so the console cannot import it. This card therefore names languages through Intl.DisplayNames rather than re-typing the ten strings. Measured in this container, Intl + capitalisation reproduces all ten hand-kept labels exactly, so the two agree today and nothing re-derives that they still will. Taker: objectui#9954 is in flight on packages/app-shell/src this round; a one-line barrel export would close it. Not filed as a card because it fits none of the three filing classes (no reproducible defect, no declared-contract violation, no metadata trap) -- if the seat disagrees, dedupe words: localeLabel, app-shell barrel export, native language name, LocaleSwitcher, console reuse.", "noted, not filed: this card's `Blocked-by:` line still names a platform card that stopped being the blocker on 2026-09-05; two seats recorded that at the time and the 2026-09-18 unlock released it on that stale line. Not a code defect and not mine to edit -- the card face belongs to the triage seat or the seat landing this PR. Taker: the seat that lands PR 10050.", "noted, not filed: under the DOM environment these console tests run in, `userEvent.selectOptions` leaves a controlled select's value unchanged and a `userEvent.click` on a submit button never reaches the form's onSubmit. Both were measured here and both fail SILENTLY -- the assertion downstream just reads the unchanged state. This repo's other form tests already use fireEvent for exactly this, so it is a known shape rather than a new one; noted because a reader of my test file will wonder why fireEvent. Taker: none -- it is a property of the environment, not of any file a PR will touch.", "PR-BODY CORRECTION for the seat (dev writes the body once and does not PATCH): the body's 'Not measured here' paragraph says the two project suites were not run because the verify lock timed out. That was true when the PR was opened and is now stale -- the slot was re-taken and both ran green afterwards: packages/i18n 69 files / 1145 tests, apps/console 103 files / 1173 tests, both exit 0. The paragraph should be replaced with those two readings, leaving the browser/live-stack line as the only genuinely unmeasured item." ] }Generated by Claude Code
Generated by Claude Code
os-tesla commentedon Sep 20, 2026
ACCEPTED on review — PR objectui#10050, head
dfb29eb68. Reviewed by thedomain:ui#2execution seat (PM sessionsession_018HrVaotisyhgmot9o2MLRq) at 2026-09-20T07:04Z. Implemented-by:os-devsubagent · Reviewed-by: this seat. Landing armed: flipped ready early, auto-merge (squash) enabled. ⛔ No approval submitted, ⛔ no hand merge.What the seat verified FIRST-HAND, ⛔ not on the dev's word
① The load-bearing claim — 「the write CANNOT use the same writer as
name」. Read onorigin/main:packages/auth/src/createAuthClient.ts'supdateUserisawait betterAuth.updateUser(userData)— a thin pass-through — andpackages/auth/src/AuthProvider.tsxhands that straight through. objectstack's own platform notes say in so many words that better-auth has nolocaleon its user model. ⇒ alocalewritten throughupdateUserwould have nowhere to land. The separate card with its own submit is ⛔ not a style choice; it is the only shape available, and it is the onePasswordCardalready uses in this same file for the same reason.② 「no second language list」.
offerableLanguagesis a member ofpackages/i18n's provider, andpackages/app-shell'sLocaleSwitcheris its other reader. This card reuses it ⇒ one source of truth for the offered set, as the order required.③ The dev's own caveat about that set is accurate, and I re-derived it. The provider builds
offerableLanguagesasappLocales.filter(canResolveLanguage)— an intersection of the deployment's published locales with what this renderer can resolve, with the built-in packs as the fallback when the app answers nothing resolvable. ⇒ a locale a deployment publishes for messaging but ships no console pack for is a valid delivery tag this control will not offer. Recorded here as a known, measured narrowing; ⛔ not filed as a card, because nothing today measures a deployment where the two sets differ.④ Scope. 13 files, all inside the declared surface (
ProfilePage.tsx, its new pins, ten locale packs, one changeset).check-governed-merges.mjsre-run on the FINAL file list: 0 of 13 paths hit the register ⇒ NOT governed.What the seat did ⛔ NOT verify
locale」 rests on the published packages (plugin-security's member-default set andsys_user_selfatoperation: "all",plugin-auth's{name, image, locale}whitelist) and on the permission instrument — ⛔ not on a live 200.Three things the seat carried, so nothing lives only in a hand-back
sys_user.localeone setting or two?」, in the decision inbox with its 维护者速读 and four-facet block. ⭐ Per the half-state patrol's H52 remedy this is a new card, ⛔ never aneeds-user-decisionre-hung on this one.Blocked-by:line is gone. It had named a non-blocker since 2026-09-05; the body now carries the record of why, and ⛔ nothing was silently deleted.packages/i18n69 files / 1145 tests,apps/console103 files / 1173 tests).Generated by Claude Code
os-tesla commentedon Sep 20, 2026
LANDED and content-verified on the merged ref.
domain:ui#2execution seat, 2026-09-20T08:06Z.mergedandremoved_from_merge_queuecarry the same secondc2f0f4832has one parent (23b99585f) and the(#10050)title suffix — ⛔ not read from the API fieldThe content reading, control lit on BOTH refs
localewrite (update('sys_user'…)en.tskeyssetNameinProfilePage.tsx23b99585f(the ref this landed on)c2f0f4832(merged)⇒ both subjects moved while the control held at 3 on both refs — the existing name card is untouched, and the language card writes through the data adapter rather than through
updateUser, exactly as reviewed.State closed out in the same act:
pm:dispatchedstripped, assignee released;domain:ui·priority:p3·enhancement·i18nstay.Still open behind this card, ⛔ not dropped
sys_user.localeone setting or two? In the inbox with its 维护者速读 and four-facet block; this seat recommends C, the dev recommended A, and the disagreement is on the card rather than hidden.Blocked-by:line on this card's body was removed while landing, with the record of why kept in place of it.Generated by Claude Code
feat(console): a "Language" item on the profile page for the user’s o…