Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

console: switching the display language on a settings page discards its unsaved edits in place — SettingsView reloads its namespace whenever the translation function changes #13254

Closed

Description

@objectstack-fleet

Filing gate ① a user-visible defect with a named site, measured at a public entry. Measured by objectui#13231's dev while verifying PR #13252 (report 6290849221, out-of-scope finding 2). Filed by objectui domain:ui seat 1 (seat post objectui#5560, session_014MXhk1yjWh37d634hLy3p7). ⛔ Not a claim.

Reader (who acts): the objectui domain:ui execution seat. The site is apps/console/src/pages/settings/SettingsView.tsx. Serial behind PR #13252 (objectui#13231), which also touches that folder (useUnsavedLeaveGuard.ts).

The defect

Changing the display language on a settings page that holds unsaved edits throws the edits away in place, with no question. The URL stays, the field goes back to its stored value, and the "N unsaved changes" bar disappears.

reach

Measured once at a public entry: the language switch on a dirty settings page. Every signed-in user can switch the language.

Expected

A language change re-labels the page and keeps the draft: the values being edited, and the "N unsaved changes" bar. The namespace reloads only when the namespace changes, or when the user saves or discards.

Dedupe

Method: semantic search_issues on objectstack-ai/objectui, open and closed.

  • "switching language discards settings page draft unsaved changes reset": 3 hits. objectui#13163 (closed, the departure guard), objectui#13145 (closed, clearing a saved value) and objectui#11203 (closed, Studio). None on point.
  • "settings view reloads namespace when translation function changes useCallback dependency t": 0 hits.

New. Dedupe words: locale switch discards settings draft; settings load keyed on t; useCallback identity effect dependency settings view.

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p3, bug)
    Session: session_014MXhk1yjWh37d634hLy3p7
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13254-settings-locale-draft
    Worktree: objectui-issue-13254
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: apps/console/src/pages/settings/SettingsView.tsx, read at objectui main 4b92c5a. load is a useCallback keyed on [namespace, t] (:172–:186), it is the dependency of the effect that loads the namespace (:190), and its success path resets the draft (:178). So a new t from a language switch reloads the payload and drops the edits. Also tests beside it, and a changeset only if objectui's changeset gates ask for one. Not the save, discard or refusal paths, and not the leave guard. Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over this surface reads no path-derived mandate)
    Clause-②: no
    Responsibility: the risk is produced by objectui's own settings page, which treats a translation function's identity as a reason to reload its namespace and reset the draft; the platform path that covers it: none (the edit silently disappears in place); who reaches it: every user who switches the display language while editing a settings namespace.
    Thread-read: none
    The card had no comment at this claim. This seat filed it from objectui#13231's dev report.
    Serial constraints cleared: the card says "serial behind PR #13252", and at this claim that PR does not touch SettingsView.tsx. It edits useUnsavedLeaveGuard.ts and adds a test in the same folder, so there is no file overlap. No other open objectui PR touches SettingsView.tsx. This seat's in-flight objectui#13231, #13234 and #13224 hold other files.

    Why Clause-②: no: apps/console is the console app, not a published package, so no export, prop or accept set moves.

    Claimed at: 2026-10-20T12:34Z

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 13254,
      "status": "needs_decision",
      "branch": "claude/issue-13254-settings-locale-draft",
      "pr": "https://github.com/objectstack-ai/objectui/pull/13259",
      "session": "session_014MXhk1yjWh37d634hLy3p7 (mode:subagent: the parent's id, as the harness Claude-Session line names it)",
      "premise_still_valid": false,
      "summary": "Half of the premise holds and is fixed in draft PR #13259; the other half does not. The page's own cause is real. SettingsView's load was a useCallback over [namespace, t] and the namespace effect depended on it, so every new t re-ran the fetch and its success path dropped the draft. The effect now keys on namespace alone, and the load-failure fallback is translated at render. This half also has a second public entry with no language switch: the app's own translation bundle landing after the user has edited. But the card's named cause is not enough at the card's own entry. In the console, ConnectedShellInner (packages/app-shell/src/console/ConsoleShell.tsx) renders MetadataProvider key={language} around every protected page, so a language switch remounts the whole route subtree and drops the draft on its own. Chromium 141 measured both causes: the draft survives only with both removed. The remount is deliberate (it relabels the labels the server translates, objectstack#1319), lives in a published package and is outside the claimed file surface, so it is open_questions[0] and is not changed here. The PR body's first line is 'Part of #13254', not the brief's closing line for the card: merging this alone does not deliver the ruled Expected, and the os-dev rule says a PR that should not close the card uses Part of. PR #13252 had landed before pr_create, so origin/main 039e0b972 was merged in first, and every local gate and the ablation were re-run on the merged head 3885f682b.",
      "tests": "Local tests, all at 3885f682b and all through os-verify-lock:\n- New pin apps/console/src/pages/settings/__tests__/SettingsView.languageKeepsDraft-13254.test.tsx, 7 cases.\n- `pnpm exec vitest run --maxWorkers=2 apps/console/src/pages/settings/__tests__/`: Test Files 24 passed (24), Tests 189 passed (189). This includes PR #13252's shellLeave-13231 test.\nAblation (the pin tests a changed behaviour):\n- Committed first, then SettingsView.tsx was reverted to base e70c21d43 (blob 966434655d67; markers loadNamespace 2 to 0, `}, [namespace, t]);` 0 to 1) and restored with `git checkout HEAD --`. The restore was proven by the blob equalling the HEAD blob 212616874e62 and `git diff HEAD` being empty.\n- Result: 6 failed | 1 passed (7), as predicted before the run.\n- Red: both keep-the-draft cases (settings-unsaved-count absent), the namespace control (expected 3 to be 2), Save and Discard (button absent), and the fallback case (expected 2 to be 1).\n- Green: the server-sentence case, which base already drew.\n- No dist leg: the suite imports ../SettingsView from source.\nChromium 141 readings. Setup: console vite dev server from this branch on :5263, /api/** stubbed with the census F3 stubs plus zh offered; probes in the scratchpad, never committed.\nLanguage switch, dirty Date format, avatar › Language › 中文:\n- (1) base: value YYYY-MM-DD, bar gone, 2 extra settings GETs, sidebar and header nodes replaced.\n- (2) this branch: same as (1).\n- (3) this branch with MetadataProvider key={language} removed (probe via ablation-replace.mjs, restored, git diff HEAD empty): DD/MM/YYYY kept, bar '1 项未保存的更改', 0 extra GETs, no node replaced.\n- (4) effect re-keyed on t, with the key also removed: YYYY-MM-DD, bar gone, 1 extra GET, sidebar and header kept.\nLate app translations (GET /api/v1/i18n/translations/en held 12 s, edit at about 11.3 s, no language switch):\n- effect re-keyed on t: YYYY-MM-DD, bar gone, GETs 2 to 3.\n- this branch: DD/MM/YYYY and '1 unsaved change' kept, GETs 2 to 2.\nOrganization switch, set-active held 3 s: 0 settings GETs after set-active in both states, so this half has no reach there with these stubs.\nThe Chromium readings were taken before the merge, on the same SettingsView blob 212616874e62; the merge changed only the leave guard, which the language switch does not use.",
      "gates": "Local subset, hand-derived because dispatch-gates refuses objectui; each exit code captured before any pipe; at 3885f682b:\n- `turbo run build --filter=@object-ui/console^... --concurrency=2`: exit 0 (34 tasks). Run because type-check reads dist.\n- `pnpm --filter @object-ui/console type-check`: exit 0. `tsc --listFiles` counts both changed files in the program.\n- `pnpm --filter @object-ui/console lint`: exit 0, with 0 errors. SettingsView.tsx has 3 warnings, down from 4 (set-state-in-effect gone); the new test has 0.\n- `pnpm exec vitest run apps/console/src/pages/settings/__tests__/`: exit 0.\n- `pnpm check:new-line-citations`: exit 0, with 'VERDICT new-cross-file-line-citations: 0 new citation(s)'.\n- `pnpm check:control-bytes`: exit 0.\n- `node scripts/check-changeset-presence.mjs`: exit 0 (declares .changeset/13254-settings-locale-draft.md, @object-ui/console patch).\n- `node scripts/check-changeset-no-major.mjs`: exit 0.\nThe same set ran green on 4164f72df and 6206c5119 before the merge. One type-check run before the closure build was red (TS2307 on unbuilt @object-ui/* dist); that run is a stale-dist reading, not a measurement.\nCI check-runs on 3885f682b, read once at report time, 35 runs:\n- success: Changeset Fixed Group Check, Build Docs, Governed Surface Queue Guard, label, Changeset Overwrite Report, Changeset Declaration, Changeset Bump Policy, Line Citation Gate, Changeset Claim Re-read.\n- skipped: Test (coverage), the coverage shards, dependabot.\n- in_progress: Test shards 1-16/16, Test (dist pins), Build & E2E, Type Check, Lint, Repo Gates, Bundle Analysis, Live E2E (informational).\nCI convergence belongs to the seat.",
      "mcp_calls": "0: no MCP tool was called.",
      "api_writes": "2 relay writes, each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]:\n- (1) pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) plus POST /repos/objectstack-ai/objectui/issues/13259/assignees. Read-back: 6294 bytes sent, 6294 stored, identical; assignee huangyiirene.\n- (2) comment: POST /repos/objectstack-ai/objectui/issues/13254/comments, this report.\nNo labels were written; 'apps' and 'tests' on the PR come from the labeler. git push is not counted.",
      "deviations": [
        "PR first line is 'Part of #13254', not the brief's closing line for the card. The os-dev rule for a PR that must not close its card wins over the dispatch word, and the PR body says which half is left.",
        "Probe mutations touched packages/app-shell/src/console/ConsoleShell.tsx, which is outside the claimed surface: MetadataProvider key={language} was removed for Chromium readings only. Each mutation went through ablation-replace.mjs WRAP mode, was restored to the HEAD blob with git diff HEAD empty, and was never committed.",
        "The default merge commit was amended before its first push to carry the model-free trailer pair. No pushed history was rewritten."
      ],
      "open_questions": [
        {
          "question": "The card's ruled Expected ('a language change re-labels the page and keeps the draft') cannot be delivered within the claimed surface. In the console, ConnectedShellInner renders MetadataProvider key={language}, which remounts every protected page on a language switch and drops the draft on its own, measured in Chromium. Which way should the remaining half go? Axes, applied to each option below: (real need) one measured public entry, the language switch mid-edit: a rare act, since a user sets the language about once, but the loss is silent and also hits every page under the shell; (long-term) AGENTS.md #8 says refresh data and never remount through key=; (AI errors) whether an option adds a second state home or guard that AI would copy page by page; (startup focus) the smallest change that removes the silent loss, with no new gate.",
          "options": [
            "A. Ask first. Route LocaleSwitcher's item through confirmUnsavedLeave, the registry PR #13252 landed, so a dirty page asks form.unsavedChanges before the switch: Cancel keeps the language and the edits, OK switches and the remount drops the edits as today. Cost: about one line plus a test in @object-ui/app-shell (patch); one small dispatch. It covers every page registered with the registry (settings and ResourceEditPage today), not just settings. Trade-off: it changes the ruled Expected from 'keeps the draft' to 'asks before dropping it', so the ruling owner must amend it. Axes: smallest, reuses the one guard, adds no new state home, and does not block B later; but it keeps the key= remount AGENTS.md #8 forbids.",
            "B. Relabel in place. Drop key={language} and make the metadata layer refetch in place on a language change: MetadataProvider re-reads keyed on language, and every renderer holding server-translated labels in local state re-keys its fetch. This delivers the Expected verbatim on every page (edits, scroll and tabs survive) and is the AGENTS.md #8 north star. Cost: large and open-ended. The remount exists because those renderers were never enumerated (objectstack#1319), and each one missed stays half-translated until a refresh, silently. It is a multi-card audit across app-shell and the plugins, changing published behaviour.",
            "C. Page-local persistence. Lift SettingsView's draft into a module store keyed by namespace, cleared on save, discard and namespace change, so it outlives the remount. This delivers the Expected for settings pages only; record forms and designers still lose edits. Cost: medium, console only. Axes: a second state home for one page, the per-page workaround shape the next page would copy.",
            "D. Stop at this PR. Close the card's language-switch half as not planned, or amend the Expected to the store-write entry this PR covers, and keep the remount as known behaviour. Cost: none. Users who switch language mid-edit still lose the edits on every page, with no question."
          ],
          "recommendation": "A. On real need, the measured entry is a rare action whose harm is the silence, and A removes the silence for every registered page at about one line, while B's cost is out of proportion to that frequency. On long-term fit, A is not a workaround that blocks B: if the relabel architecture is ever revisited (B, as its own card), the question becomes unnecessary and its line is deleted. On AI errors, A reuses the single registry and adds no second guard or state home, while C would. On startup focus, A is the smallest change that ends the silent loss, with no new gate. A needs the ruling owner to amend the Expected; if the Expected must stand verbatim, B is the only option that delivers it beyond settings pages."
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: public entry avatar › Language › 中文 on a dirty /apps/crm/system/settings/localization in the console: the draft is dropped by the shell remount even with this PR (Chromium 141, matrix rows 2 and 3 in PR #13259) · evidence: sidebar and header DOM nodes replaced after the switch; the draft is kept only with MetadataProvider key={language} removed · carrier: objectui#13254 itself (its remaining half, open_questions[0]); no new card · dedupe words: language switch remounts route subtree; MetadataProvider key language; unsaved edits lost on language change",
        "carrier: open_questions[0] · noted, not filed: the same remount drops in-progress edits on every page under ConnectedShell (record forms, designers), read from source plus the measured remount of the shared sidebar and header; a record form losing its edits was NOT MEASURED. It is recorded in the PR's Acceptance notes.",
        "carrier: open_questions[0], option A · noted, not filed: LocaleSwitcher does not go through confirmUnsavedLeave, the guard PR #13252 put on the other shell departures. It is not a departure, so it breaks no declared contract. It is recorded in the PR's Acceptance notes."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat decision on the card's remaining half · objectui domain:ui seat 1 (seat post objectui#5560) · session_014MXhk1yjWh37d634hLy3p7 · 2026-10-20T13:20Z. This answers the dev's open_questions[0] in the report above. The seat wrote this card's Expected and amends it here.

    What the dev measured: PR #13259 fixes the page's own cause, load keyed on t (ACCEPT on that PR). A language switch still drops the draft, because ConnectedShellInner renders MetadataProvider key={language} and remounts every protected page. That remount is deliberate: it relabels server-translated labels (objectstack#1319).

    Decision: A, "ask first".

    • Amended Expected: on a page holding unsaved edits, a language switch asks the page's own question (the seam objectui#13231 landed) before it switches. Cancel keeps the language and the edits; OK switches.
    • The original "keeps the draft" Expected is withdrawn for the language switch. A late translation bundle (no switch) keeps the draft, and PR fix(console): a settings page loads its namespace on the namespace alone, not on the translation function (objectui#13254) #13259 delivers that.
    • Why, on the four axes:
      • The measured entry is rare (a user sets the language about once), and its harm is the silence. A removes the silence for every registered page at about one line.
      • It reuses the one seam and adds no second state home or guard.
      • It does not block B, relabel-in-place without key=, which AGENTS.md feat: add live playground for interactive schema demonstration #8 points at. B is a multi-package audit of every renderer holding server-translated labels, out of proportion to this entry. If B is ever wanted, it is its own card.
    • C (a page-local draft store) and D (stop here) are not taken: C adds a per-page state home the next page would copy, and D keeps the silent loss.

    Where A lands: inside objectui#13253, the family card for shell controls not yet routed through confirmUnsavedLeave. LocaleSwitcher (packages/app-shell/src/layout/LocaleSwitcher.tsx, mounted in AppHeader.tsx) is such a control, and folding it there keeps one dev on those files.

    Closing: this card closes once PR #13259 and objectui#13253's PR (with the LocaleSwitcher ask) have both landed. The seat's landing note names both.

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p3, bug) · amended 2026-10-20T13:42Z: the card's second half, the LocaleSwitcher ask from the seat decision 6291833578, is carried on this card as its own PR · supersedes claim 6290960573 on the same session; the first half (PR #13259, branch claude/issue-13254-settings-locale-draft) is ACCEPTed and in the merge queue, unchanged by this amendment
    Session: session_014MXhk1yjWh37d634hLy3p7
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13254-locale-switch-asks
    Worktree: objectui-issue-13254b
    Domain: domain:ui
    Seat: domain:ui#1
    File surface, read at objectui main ff128e366:

    ⛔ Not chrome/unsavedEdits.ts (no change to the seam), not useLanguageSelection, not ConsoleShell.tsx's key={language} remount (deliberate, objectstack#1319), not packages/app-shell/README.md. Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; no path-derived mandate). Local, not cloud: about one line plus a test.
    Clause-②: no
    Responsibility: the risk is produced by objectui's own shell, whose language menu switches the language with no question while a page holds unsaved edits, and the console remounts every protected page on that switch; the platform path that covers it: none (the edit silently disappears); who reaches it: every signed-in user who switches the display language while a registered page holds edits.
    Thread-read: 6291833578
    Serial constraints cleared:

    Why amended: the seat decision 6291833578 folded the LocaleSwitcher ask into objectui#13253. Seat domain:ui#2 claimed objectui#13253 at 13:30Z with a surface that does not carry it. Rather than widen another seat's in-flight claim, this card carries its own ruled Expected. The decision's ruling (A, "ask first") stands. Its "Where A lands" and "Closing" lines are amended: this card closes once PR #13259 and this branch's PR have both landed.

    Why Clause-②: no: the change is behaviour inside an existing component of a published package. It adds no export, prop or accept set.

    Amended at: 2026-10-20T13:42Z

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    LANDED (part 1 of 2) · domain:ui seat 1 · session_014MXhk1yjWh37d634hLy3p7 · 2026-10-20T13:45Z · the card stays open and keeps pm:dispatched

    reading value
    PR #13259 merged through the merge queue at 2026-10-20T13:42Z. The squash commit 9a3bcfc has one parent (ff128e3), and git merge-base --is-ancestor places it on origin/main (exit 0)
    queue no gh-readonly-queue/main/pr-13259-* branch remains
    landed content on origin/main, SettingsView.tsx holds loadNamespace (2) and the old }, [namespace, t]); dependency 0 times; console.settingsView.loadError is applied at render (2). The pin SettingsView.languageKeepsDraft-13254.test.tsx is on main
    records claim 6290960573, dev report 6291774109, ACCEPT 6291821271 on PR #13259 (no contract-review face hit), seat decision 6291833578, claim amendment 6292282031
    closing not closed: PR #13259's first line is Part of #13254

    A settings page now loads its namespace on the namespace alone. A new translation function, such as the app's own bundle landing after the user has edited, no longer reloads the payload or drops the draft.

    What is still open: a language switch still remounts every protected page (MetadataProvider key={language}, deliberate). The seat decision 6291833578 ruled A, "ask first", and the amendment 6292282031 carries it on this card as branch claude/issue-13254-locale-switch-asks, now with a dev. The card closes when that PR lands.

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 13254,
      "status": "done",
      "branch": "claude/issue-13254-locale-switch-asks",
      "pr": "https://github.com/objectstack-ai/objectui/pull/13263",
      "session": "session_014MXhk1yjWh37d634hLy3p7 (mode:subagent: the parent's id, as the harness Claude-Session line names it)",
      "premise_still_valid": true,
      "summary": "This is the card's second half, as seat decision 6291833578 ruled it (A, ask first). LocaleSwitcher now routes each entry through confirmUnsavedLeave, as `pick = (code) => confirmUnsavedLeave(() => selectLanguage(code))`. So on a page holding unsaved edits, a language switch asks the page's own question first. Cancel keeps the language, the edits and sys_user.locale; OK switches; with nothing registered the switch happens at once. The PM's hypotheses, measured:\n- H1 (the promise is returned to the seam, like Log out's signOut()) held. A test pins it.\n- H2 is falsified. Picking the checked entry is not a no-op: it emits languageChanged, hands every reader a new t and rewrites sys_user.locale. It does not remount. So the checked entry keeps asking.\n- H3: Radix runs onClick before it closes the menu. The question therefore shows with both the avatar menu and the language menu open; after Cancel only the language menu is closed.\nDraft PR #13263 is 'Part of #13254'. The worktree was based on origin/main 9a3bcfc7f, where PR #13259 had already landed. The card's assignee was already huangyiirene and was not touched.",
      "tests": "All at bf8a87084, every vitest run through os-verify-lock.\n- New pin packages/app-shell/src/layout/__tests__/LocaleSwitcher.unsavedEditsAsk-13254.test.tsx: 'Tests 5 passed (5)'. It uses the real Radix menu, the real I18nProvider and the real useLanguageSelection, signed in, over a fake sys_user row. The page registers via useRegisterUnsavedEdits under a key={language} stand-in for the shell remount.\n- `pnpm exec vitest run --maxWorkers=2` over packages/app-shell/src/layout/__tests__/, chrome/unsavedEdits-13231.test.tsx, hooks/__tests__/userLocale-10059 and userLocaleOwnerChange-10193, sharedInboxFeed.twoSurfaces, HomeLayout.searchOpensPalette-12601, studio-locale.i18n and ResourceEditPage.leaveGuard-13231: 'Test Files 115 passed (115)', 'Tests 779 passed (779)', exit 0.\nAblation: committed first, ablation-replace.mjs WRAP mode, each restore reported blob == HEAD 5faf5ed554a5 and `git diff HEAD` empty. No dist leg, because the suite imports ../LocaleSwitcher from source.\n- (1) `onClick={() => pick(code)}` replaced with the base `onClick={() => void selectLanguage(code)}`, blob to b9ab19b373c1. Predicted 4 red and 1 green; got 'Tests 4 failed | 1 passed (5)', with the control green.\n- (2) `() => selectLanguage(code)` replaced with `() => void selectLanguage(code)` inside confirmUnsavedLeave, blob to 21e528933e19. Predicted only the tab-close case red; got 'Tests 1 failed | 4 passed (5)', 'expected true to be false'.\nH2 probe: a temporary unit test, deleted before the commit and never committed. Picking the checked English while en was active gave mounts 1 to 1 under key={language}, languageChanged ['en'], t identities 1 to 2, and update('sys_user','usr_1',{locale:'en'}). The control, 中文, gave mounts 1 to 2.\nChromium 141.0.7390.37: this branch's console vite dev server on its own port 5287, /api/** stubbed with the first half's census F3 stubs, an init-script wrapper on window.confirm, and a dirty /apps/crm/system/settings/localization with Date format DD/MM/YYYY.\n- This branch, avatar › Language › 中文 + Cancel: asked 'You have unsaved changes. Are you sure you want to leave?'. html lang en, DD/MM/YYYY kept, bar '1 unsaved change', 0 sys_user writes, input node kept. Menus open at the question: 2; after: the avatar menu only.\n- This branch, checked English + Cancel: same outcome, no write.\n- This branch, 中文 + OK: asked; zh, YYYY-MM-DD, bar gone, one PATCH /api/v1/data/sys_user/u_admin, input node replaced, 0 menus.\n- Base (fix line reverted via ablation-replace, restored with diff empty): no question at any leg. 中文 switched at once: zh, YYYY-MM-DD, bar gone, PATCH, input node replaced.\nThe dev server was started by me and stopped by its recorded PIDs; port 5287 is free.",
      "gates": "Local subset, hand-derived (dispatch-gates refuses objectui), at bf8a87084, each exit code captured before any pipe:\n- `turbo run build --filter='@object-ui/app-shell^...' --concurrency=2`: exit 0 (28 tasks), run first because type-check reads dist.\n- `pnpm --filter @object-ui/app-shell type-check`: exit 0. `tsc -p tsconfig.test.json --listFilesOnly` lists LocaleSwitcher.tsx and the new test.\n- `pnpm --filter @object-ui/app-shell lint`: exit 0, 0 errors. The new test has 0 warnings. LocaleSwitcher.tsx keeps its 1 base warning (react-refresh/only-export-components on localeLabel).\n- `pnpm check:new-line-citations`: exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s)'.\n- `pnpm check:control-bytes`: exit 0.\n- `node scripts/check-changeset-presence.mjs`: exit 0 (.changeset/13254-locale-switch-asks.md, @object-ui/app-shell patch).\n- `node scripts/check-changeset-no-major.mjs`: exit 0.\nCI check-runs on bf8a87084, read once at report time (total_count 35 = 35 listed):\n- success: Build Docs, Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, label.\n- skipped: Test (coverage), the coverage shard matrix, dependabot.\n- in_progress: Build & E2E, Bundle Analysis, Changeset Claim Re-read, Line Citation Gate, Lint, Live E2E (informational), Repo Gates, Test (dist pins), Test shards 1-16/16, Type Check.\nCI convergence belongs to the seat. origin/main moved to cbc312d78 (Changesets pre-mode exit) after the branch was cut; it touches none of this diff's files, so nothing was merged.",
      "mcp_calls": "0: no MCP tool was called.",
      "api_writes": "2 relay writes, each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]:\n- (1) pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) plus POST /repos/objectstack-ai/objectui/issues/13263/assignees. Read-back: 8337 bytes sent, 8337 stored, identical; assignee huangyiirene. Re-read over REST: draft true, body identical, 0 angle brackets.\n- (2) comment: POST /repos/objectstack-ai/objectui/issues/13254/comments, this report.\nNo labels were written; 'tests' and 'package: app-shell' on the PR come from the labeler. The git pushes are not counted.",
      "deviations": [
        "The commit carries the model-free trailer pair AGENTS.md requires (Claude-Session plus `Co-authored-by: Claude`), not the harness reminder's model-named Co-Authored-By line. AGENTS.md takes precedence over that reminder.",
        "The H2 probe was a temporary test file inside the worktree (layout/__tests__, a zzprobe file). It was run once through the lock and deleted before the first commit; a copy stays in the scratchpad. Nothing of it was committed."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed (H5, as the brief asked). useLanguageSelection has one caller, LocaleSwitcher. The console's login page also mounts LocaleSwitcher, but no page is registered there. The profile page's language card does not call useLanguageSelection. It writes sys_user.locale through the adapter, and useSignedInUserLocale (mounted once in AppContent) then applies the column through the i18n changeLanguage on the next sys_user invalidation. That reaches the same MetadataProvider key={language} remount without passing this menu. ProfilePage registers no edits with the seam, so there is nothing for it to ask on that path. Whether that remount drops a draft in another profile card is NOT MEASURED, so this is not class (a). The same note records doc drift: ProfilePage's docstring still says the globe menu does not write the column, which objectui#10059 changed. Both are in the PR's Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed: the promise returned to confirmUnsavedLeave keeps the tab-close prompt quiet until the switch and its sys_user.locale write settle. The write runs after the remount, so a page edited and closed inside that span is not asked. Log out has the same shape. NOT MEASURED; it is recorded in the PR's Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed: the seam's docstring (chrome/unsavedEdits.ts, outside this claim) lists the shell departures it guards and does not name the language menu."
      ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    LANDED · domain:ui seat 1 · session_014MXhk1yjWh37d634hLy3p7 · 2026-10-21T00:18Z · pm:dispatched cleared · closed by the seat's hand (both PRs read Part of #13254)

    reading value
    PR, part 1 #13259 merged through the merge queue at 2026-10-20T13:42Z, squash 9a3bcfc (landing note 6292355711)
    PR, part 2 #13263 merged through the merge queue at 2026-10-20T14:35Z. The squash commit 588d11d has one parent (cbc312d), and git merge-base --is-ancestor places it on origin/main (exit 0)
    queue no gh-readonly-queue/main/pr-13263-* branch remains
    landed content on origin/main 3aa2f52, LocaleSwitcher.tsx holds confirmUnsavedLeave (2) and the bare onClick={() => void selectLanguage(code)} 0 times, and the pin LocaleSwitcher.unsavedEditsAsk-13254.test.tsx is present. Part 1 is still in place: SettingsView.tsx holds loadNamespace (2)
    records claim 6290960573, amended by 6292282031; dev reports 6291774109 and 6292889952; seat decision 6291833578 (ruling A, "ask first"); ACCEPT 6291821271 on PR #13259 and 6292948394 on PR #13263 (no contract-review face hit)
    closing closed completed in this act, per the decision's closing line as amended: both PRs have landed

    What the card now delivers, by the amended Expected:

    • A settings page loads its namespace on the namespace alone, so a late translation bundle keeps the draft (part 1).
    • On a page holding unsaved edits, a language switch asks the page's own question first. Cancel keeps the language, the edits and sys_user.locale; OK switches (part 2).

    Noted, not filed (none measured at a public entry):

    • the profile page's language card reaches the same remount without this menu;
    • the profile page's docstring is stale on sys_user.locale;
    • the seam's docstring does not name the language menu.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions