Repository navigation
fix(console): after a language switch, date cells format in the new language, not the stale server locale - #27624
Merged
Conversation
…wn row is written The language switch remounts LocalizationFetchProvider and writes sys_user.locale afterwards; the remount's read could answer the old language, so grid date cells stayed English under zh-CN until a reload. Claude-Session: https://claude.ai/code/session_01Utp6CakU47s2AYQsrXttMo Co-authored-by: Claude <noreply@anthropic.com>
…d cases Claude-Session: https://claude.ai/code/session_01Utp6CakU47s2AYQsrXttMo Co-authored-by: Claude <noreply@anthropic.com>
…-b2omyd Claude-Session: https://claude.ai/code/session_01Utp6CakU47s2AYQsrXttMo Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC 席位核对ACCEPT · head
Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Requested by Bill · project thread
Fixes #27498
Clause-②: no
Metadata-protocol: none
维护者速读
/auth/me/localization;日期格式化器仍只有一个(formatDate经useDisplayLocale),未新增第二个。sys_user.locale;重挂载发出的读取可能先于写入到达服务器,拿回旧的en-US并整个会话保留,于是中文界面下表格日期显示Oct 11,整页加载才变2026年10月11日。ui/apps/console/src/LocalizationFetchProvider.tsx(新的useDataInvalidation依赖与signal一行);LocalizationFetchProvider.test.tsx新的 describe 块。pnpm --dir ui exec vitest run apps/console/src/LocalizationFetchProvider.test.tsx,预期 18 个测试全绿,其中 4 个新用例名以re-reads after a write开头。What changes
LocalizationFetchProvider(console) now readsGET /api/v1/auth/me/localizationagain whenever the signed-in user's ownsys_userrow is written through the data-invalidation bus, and that re-read opts out of joining a request still in flight from before the write.Cause, measured on a running stack. The endpoint's
localeis the caller's language: their ownsys_user.localewhen set, else the request'sAccept-Language, else the deployment default.useDisplayLocaleranks it above the active UI language, and every grid / record date renderer formats with it. The avatar-menu switch (useLanguageSelection) changes the UI language first and writessys_user.localesecond. The UI-language change remounts the metadata subtree (key={language}inConnectedShellInner), which remounts this provider, and the remount's read can reach the server before the column write commits. It then answers the previous language (for a user with no stored language yet, the browser'sAccept-Language,en-US), and nothing ever read again. Result: Chinese headers, chips and footer (allt-driven) besideOct 11/Nov 1/Nov 18dates, until a full page load re-read the now-written column.Why the record page differed. It is not a second formatter and not grid-specific. The record page reads the same
useDisplayLocale; the walkthrough's record page was a fresh page load, whose boot read sees the committed column. Reached in the same session by clicking the row, the record page also readOct 11, 2026before this change (measured below).Fix at the cause. The provider subscribes with
useDataInvalidation('sys_user', userId)(the same bususeSignedInUserLocalealready reads; every adapter write reaches it throughuseMutationInvalidationBridge). The language switch's own write therefore triggers a read that starts after the write has settled. BecausesharedGetJsonshares a promise with any overlapping GET of the same URL, a re-read passes anAbortControllersignal, which opts it out of sharing (and aborts it if superseded or unmounted); the boot read keeps sharing withseedTenantLanguage()(objectui#5544). The existingcancelledguard drops a stale answer that settles late; the catch now returns first when cancelled so an abort is not treated as a transient failure. The profile page's language card writes the same row and is covered by the same subscription.The ruling "Format grid date cells through the same locale-aware formatter the record page uses. No second date formatter." holds: no formatter, renderer or
useDisplayLocaleline changed.Tests
New
describeblock inui/apps/console/src/LocalizationFetchProvider.test.tsx, probinguseDisplayLocale()plusformatDate('2026-10-11', undefined, { locale, alwaysShowYear: true }), the face a grid cell paints:a read that lost the race with the language write is replaced after the write: boot read answersen-US(Oct 11, 2026), asys_userwrite for the signed-in user re-reads, the probe showszh-CN|2026年10月11日.the re-read does not join a request still in flight from before the write: the boot read is held pending; the write's re-read issues its own request and landszh-CN; the held request settling late withen-USis dropped.a write to another user's row reads nothing againandsigned out, a sys_user write reads nothing again.The block resets the shared in-flight registry after each case, so a pending request from a failing case cannot be joined by the next one.
Targeted runs and ablations (head eda50a4, before the main merge; re-run after it in Verification)
pnpm --dir ui exec vitest run apps/console/src/LocalizationFetchProvider.test.tsx→Tests 18 passed (18); withapps/console/src/__tests__/localizationBootDedup.test.tsx→Test Files 2 passed (2),Tests 21 passed (21).scripts/ablation-replace.mjs: effect dependencies[endpoint, userRowWrites]→[endpoint](anchor 1 → 0, blob changed). Red:a read that lost the race…andthe re-read does not join…. Restored; file hash equal to HEAD's.signal: reread?.signal,(anchor 1 → 0). Red:the re-read does not join a request still in flight from before the writeonly (Tests 1 failed | 17 passed). Restored; hash equal to HEAD's.distinvolved: the console suite resolves the provider and@object-ui/*from source.Acceptance notes
sys_user.localewhose UI language comes from the device (for example 中文 picked on the sign-in page) getsNov 18dates under Chinese headers on every load, deterministically: the endpoint answers theAccept-Languagerung (en-US), anduseDisplayLocaleranks it above the UI language. Measured on this stack with the column reset to null andobjectui-locale=zh. Closing it means changinguseDisplayLocale's documented precedence or having the endpoint say which rung answered, a design call outside this card.useDisplayLocaledocblock says the tenant locale is "Frequentlyundefined"; on this stack an authenticated read always answered a locale (en-USfrom the header or deployment rung). Not edited here.A unit of work inside a project.) is the showcase bundle's coverage, out of scope per the card.Verification
Live reproduction, app-showcase on this branch (backend :4127 --fresh, console vite :5197 from ui/apps/console source)
Script: sign in as admin in an
en-USbrowser, open/apps/showcase_app/showcase_task(All Tasks), avatar menu → Language → 中文, read the Due Date cells; click "Audit current IA" (same session); then load that record URL fresh. Thesys_userPATCH was held 1.5 s by a Playwright route to make the race deterministic; a second run held it 0 s. Column reset to null before each run.Before the fix (head 556f323, delay 1.5 s):
/auth/me/localizationanswers:en-US,en-US,en-US(remount read), then the PATCH{"locale":"zh-CN"}lands.截止日期; cellsNov 18, Nov 8, Nov 1, Nov 28, Oct 11, Oct 31.Oct 11, 2026. Same record by URL load:2026年10月11日, and the grid behind it11月18日, 11月8日….After the fix (delay 1.5 s):
en-US,en-US, PATCH released,en-US(the remount's stale read),zh-CN(the re-read).11月18日, 11月8日, 11月1日, 11月28日, 10月11日, 10月31日; record by click2026年10月11日; by URL load2026年10月11日.After the fix, delay 0: cells
11月18日…, record by click and by URL2026年10月11日.Local gates on head 416229c (after merging origin/main); the farm is CI's
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(3 paths vs merge base 1ed3360). Reconciliation--ran: 33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN.check:*,check-sdui-manifest.mjsand its--self-test, the changeset gates,check-closing-keyword-parity.mjs, the release self-tests).pnpm --dir ui exec vitest run scripts/: exit 1,2 failed | 4899 passed; both failures areTest timed out in 15000msin tree-wide scans run while a dev backend, a vite server and the gate loop shared the box (check-hand-rolled-comment-mask.test.ts"exits 0 as a process, which is what CI reads",check-vi-mock-inherit.test.ts"no@object-ui/i18nfactory freezes the surface"). Re-run alone under the verification lock:Test Files 2 passed (2),Tests 105 passed (105). The diff adds onevi.mock('@object-ui/auth', …)factory, which spreadsimportOriginal().pnpm --dir ui exec vitest run apps/console/src/→Test Files 262 passed (262),Tests 2599 passed (2599);pnpm exec eslint --no-inline-configon both touched files → exit 0;pnpm --filter @object-ui/console type-check→ exit 0 afterpnpm --filter '@object-ui/console^...' build(a first run before that build exited 2 on unresolved@object-ui/*declarations only). The consoletsconfig.jsonincludessrcwith no exclude, so the test file is compiled.pnpm lint.Generated by Claude Code