Skip to content

fix(app-shell): keep a language picked on the sign-in page when someone else last used the browser - #30397

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-6njg67
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-6njg67

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread
domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

维护者速读

  • 改了什么:在登录页选的语言,换了一个人登录后不再被清掉,进系统后仍是刚选的语言。
  • 为什么改:共用电脑上,上一个人登录过,下一个人在登录页选法语,进去却变回中文,选择还被删掉(A language picked on the sign-in page is dropped when someone else last used that browser, and the sign-in list differs from the signed-in one #30338 第 3 行)。
  • 风险与回滚:只改前端一个文件;上一个人留下的语言照旧清除(objectui#10193 不变)。回滚即撤本 PR。
  • 先看:useUserLocale.ts 里新增的 keepSignedOutPickThroughOwnerChange,以及 useLanguageSelection 未登录分支。
  • 两分钟验证:浏览器先用 A 登录再退出,在登录页选 Français,用 B 登录,页面应是法语。
  • 需要你做的:无,席位落地。

Refs #30338 (item: keep the sign-in choice)

Clause-②: no
Metadata-protocol: none

What changes

@object-ui/app-shell, useUserLocale.ts only.

  • useLanguageSelection, signed out, now remembers the picked language for this page-load (module scope, memory only) and installs one listener on subscribeSessionOwnerChange.
  • When SessionUserScope.adopt drops the previous owner's state (the event fires after the sweep), the listener writes the remembered pick back into the existing objectui-locale slot. The next page-load boots from it like any choice.
  • The pick is handed to one sign-in only: the listener consumes it, a signed-in switch forgets it, and the shell's first successful sys_user read forgets it, so it cannot reach a later owner.

No new storage key, no new export from the package barrel, no request added.

Why

Measured on origin/main fdad1786, the card's premise does not hold: useLanguageResetOnOwnerChange is not on the failing path. It runs inside useSignedInUserLocale, which the console mounts only in AppContent (the /apps/:appName/* route), and the sign-in page is outside it. The drop is in the sign-in page-load itself:

  1. The footer LocaleSwitcher switch persists objectui-locale through the i18n provider's languageChanged choke point.
  2. signIn sets the user; AuthProvider calls SessionUserScope.adopt(B); the persisted pointer names A, so purgePreviousUserClientState sweeps every key outside DEVICE_SCOPED_KEYS, the pick included.
  3. LoginPage's post-login effect leaves by window.location.assign. The next page-load has no choice and no seed, so it boots in the browser language. Its pointer already names B, so no owner change fires there.

The card asked whether the previous person's residue and a pick made after they signed out can be told apart without a new stored marker. For keeping the pick, they can: the pick and the sweep happen in the same page-load, so the pick is known in memory. objectui#10193 ruling B is unchanged: with no pick since, the previous person's language is still swept (pinned below).

Acceptance notes

Tests

New packages/app-shell/src/hooks/__tests__/userLocaleSignInPick-30338.test.tsx, five cases: B picks then signs in (pick stored, A's seed swept); control with no pick (A's language swept, ruling B); the pick does not come back at a later owner change; a signed-in switch replaces it; the shell mounted with no navigation keeps it live and stored after the reset.

vitest, the locale hook files and the LocaleSwitcher layout suite (merged head 1ec0fe4)
pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/hooks/__tests__/userLocaleSignInPick-30338.test.tsx packages/app-shell/src/hooks/__tests__/userLocaleOwnerChange-10193.test.tsx packages/app-shell/src/hooks/__tests__/userLocale-10059.test.tsx packages/app-shell/src/layout/
 Test Files  143 passed (143)
      Tests  926 passed (926)
os-verify-lock: VERDICT command-exit 0
Ablation: useUserLocale.ts set to origin/main, new file run, then restored from HEAD (hash checked, git diff HEAD empty)
 × B signs in after picking: the pick stays stored for the next page-load, A’s seed does not
 × the pick goes to the next sign-in only: it does not come back at a later change of owner
 × with the shell mounted (no navigation), B keeps the pick live and stored after the reset
 Test Files  1 failed (1)
      Tests  3 failed | 2 passed (5)
os-verify-lock: VERDICT command-exit 1

The two that stay green are the ruling-B control and the signed-in-switch guard, both expected to pass with or without the fix.

Type-check
pnpm exec turbo run type-check --filter=@object-ui/app-shell --concurrency=2
 Tasks:    68 successful, 68 total
os-verify-lock: VERDICT command-exit 0

@object-ui/app-shell's type-check runs tsc --noEmit and tsc -p tsconfig.test.json, so the new test file is type-checked.

Verification

Local boot: examples/app-showcase objectstack dev --seed-admin --fresh, console from ui/apps/console via vite, Playwright with browser language zh-CN. A = the seeded admin, B = a second user. Each row is a fresh browser context; "signed out" is the auth sign-out call plus the session token and cookies removed (the owner pointer stays, as on a real sign-out). The before run swapped useUserLocale.ts to origin/main and restored it from HEAD.

Case Choice Before (origin/main) After (this PR)
Same person who last used the browser Français fr, slot fr fr, slot fr
Clean browser Français fr, slot fr fr, slot fr
A different person last used the browser Français zh-CN, slot removed fr, slot fr (and fr after a reload)
Control: different person, no pick (A had picked Deutsch) none zh-CN, slot removed zh-CN, slot removed
Clean browser Deutsch de; locales en, zh-CN de; locales en, zh-CN (unchanged, list left alone)

sys_user.locale read back empty in every row before and after: the save half is not in this PR.

Derived gates

On head 1ec0fe4c (after merging origin/main 870ebce3), node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, each exit code captured before any pipe:

exit 0  node scripts/check-adr-0087-registration.mjs --base origin/main
exit 0  node scripts/check-adr-0087-registration.mjs --self-test
exit 0  node scripts/check-changeset-no-major.mjs --base origin/main
exit 0  node scripts/check-closing-keyword-parity.mjs
exit 0  node scripts/check-closing-keyword-parity.mjs --self-test
exit 0  node scripts/check-comment-mask-corpus.mjs
exit 0  node scripts/check-empty-changeset.mjs --base origin/main
exit 0  node scripts/check-empty-changeset.mjs --self-test
exit 0  node scripts/check-sdui-manifest.mjs
exit 0  node scripts/check-sdui-manifest.mjs --self-test
exit 0  node scripts/pm/release-rehearsal-clone.mjs --self-test
exit 0  node scripts/release-pending-publish.mjs --self-test
exit 0  pnpm --dir ui exec vitest run scripts/
exit 0  pnpm --filter @objectstack/spec run check:duration-unit-keys
exit 0  pnpm check:changeset-gate-self-tests
exit 0  pnpm check:closed-ceremony-text
exit 0  pnpm check:cross-package-test-inputs
exit 0  pnpm check:dispatcher-error-vocabulary
exit 0  pnpm check:driver-memory-census
exit 0  pnpm check:engine-double-contract
exit 0  pnpm check:gitlink-declared
exit 0  pnpm check:nul-bytes
exit 0  pnpm check:published-files
exit 0  pnpm check:query-options-erasure
exit 0  pnpm check:refd-timer-probe
exit 0  pnpm check:registry-log-declared
exit 0  pnpm check:rest-log-spy-declared
exit 0  pnpm check:skill-identifier-liveness
exit 0  pnpm check:test-source-alias
exit 0  pnpm check:tier-file-adoption
exit 0  pnpm check:type-check-coverage
exit 0  pnpm check:type-check-debt
exit 0  pnpm check:undeclared-dep-imports
exit 0  pnpm check:watch-hint-literal
exit 0  pnpm check:where-matcher

dispatch-gates --ran: 35 derived famil(ies) accounted for, 35 run, 0 NOT-MEASURED. The type-check lanes, wide-population and roster families are CI's run.

main was red at dispatch on check:liveness (query/searchFields, #30367, fix in PR #30368, not landed at this merge); a Lint UI red on that row is not this PR's.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (no formatter ran; pnpm lint is not run locally, it is CI's run)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (the maintainer brief above is in Chinese by the repo's PR rule)
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…change of owner (#30338)

The sign-in page's language pick is written to the device slot, and
SessionUserScope.adopt sweeps that slot when someone other than the
browser's last owner signs in; the page then navigates away, so the next
page-load booted in the browser language. The pick is now remembered for
the page-load and written back after the sweep. objectui#10193's purge of
what the previous owner left is unchanged.

Claude-Session: https://claude.ai/code/session_01Utp6CZGT41gdDg2656as2b
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

维护者速读

席位核对 — ACCEPT · head 43bcd1f

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 09:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 09:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 6fa842a Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-6njg67 branch November 5, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant