Repository navigation
Description
epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff
Filed by the record-page epic seat, part of epic #26676. The seat walked the console on main c064d3a246, with examples/app-crm as the backend, at 1440 / 1100 / 375 px, light and dark. An independent review pass then checked each finding against the code. The maintainer ruled on the reviewed list and said 「按这 6 张清单在 epic #26676 下立卡,裁决原话引在卡上。」 and 「还有刚才立的卡可以交给ui车道处理吗?」 (2026-11-02). So this card goes to the domain:ui lane queue. Priority and area:* are left to triage. Code readings below are on main 3b920f6dd0.
Ruling
The maintainer, on amounts without a currency symbol: 「B9:金额不带货币符号:组织创建时,能根据区域识别出默认的货币吗?如果必要的话,设置页可以有一个向导。」 (2026-11-02).
The first half is already done
Yes, it is already derived. #25495 (ruling 乙, 2026-10-23) and #25651 give a new organization the currency of its creator's region. The code is packages/plugins/plugin-auth/src/organization-default-currency.ts, and the mapping is a checked-in CLDR table. The language it maps from depends on how the organization is created:
| How the organization is created | The language it maps from |
|---|---|
| Created in the console | The creator's stored sys_user.locale, else the request's Accept-Language |
| The single posture's Default Organization | The first owner's stored language or Accept-Language |
--seed-admin dev seed |
The host's LC_ALL / LC_MONETARY / LANG |
| Nothing derivable | Nothing is written. Code-less amounts then show as plain numbers, by design: no guessed symbol |
The value is written to Setup → Localization → Default currency, where an admin sees and changes it. The seat's bare-number screenshots came from a preview backend started with an empty LANG, so the seat's environment is the cause, not this code.
The gap this card closes
An organization still ends up with no currency when nothing was derivable. Two cases:
- a language tag with no country region, such as
es-419; - a self-hosted container started with no
LANG.
Then every dynamic amount shows as a bare number, and nobody is told. The renderer is right not to guess (ui/packages/fields/src/index.tsx, around line 844). The problem is that the missing setting is silent.
Target: the wizard the maintainer allowed for
The console already asks an admin once about a still-default workspace timezone. That prompt is ui/apps/console/src/pages/settings/WorkspaceTimezonePrompt.tsx with workspaceTimezonePromptLogic.ts (objectui#11758, ruling objectui#11693 B2).
- The prompt also covers the currency.
- Condition:
localization.currencyresolves from the default, with no value on any rung. - Copy, in the sense of: 本组织尚未设置默认货币,金额暂不显示币种 — offering the currency of the admin's browser region, for example 人民币(CNY).
- Confirm writes through the Settings page's own write path. Decline writes nothing. It is asked once per admin per workspace, under the prompt's existing record.
- Condition:
- When both the timezone and the currency are unset, it is one prompt, not two dialogs.
- No new settings key, no server-side "dismissed" flag (the prompt's existing ruling), and no gate.
Premise to verify first
The console can map the browser's region to a currency without a second hand-written table. Intl has no region → currency lookup, and the CLDR table lives in plugin-auth today.
- The expected route: the table moves to a shared package (
@objectstack/spec), and plugin-auth and the console both read it. - If that is not possible, stop and report a fork. Do not copy the table into the console.
Acceptance
- A workspace whose
localization.currencyis unset: the admin is asked once and offered the browser region's currency. Confirm sets it, and amounts then show the symbol. Decline leaves it unset, and the admin is not asked again. - A workspace with a currency: no prompt.
- A non-admin: no prompt.
- The existing timezone prompt tests pass, and new tests pin the currency arm and the combined case.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:ui· 2026-11-02T02:59Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Claim: PM loop round 13 · 2026-11-02T04:04Z
Session:
session_01QsR2jUHdaZrLT9hey9hCyL(card thread, os-dev); dispatched by seat sessionsession_01QsR2jUJ19MstFvFhjgiYTuAccount:
zhuangjianguoBranch:
claude/project-thread-64s6kz(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-28872Domain:
domain:uiSeat:
domain:ui#1File surface: a new
packages/spec/srcmodule holding the CLDR region→currency table and its lookup, plus its subpath index;packages/plugins/plugin-auth/src/organization-default-currency.ts(reads the table from spec, no local copy);ui/apps/console/src/pages/settings/WorkspaceTimezonePrompt.tsxandworkspaceTimezonePromptLogic.ts; theconsole.workspaceTimezonePrompt.*keys inui/packages/i18n/src/locales/*; their tests, the spec regen artifacts, and a root.changeset(stop on breach; explain in the report)Container & model: M,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: yes (widening)
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: 6567982412
Premise: to verify first, per the card (at
origin/main97680bbb: the table isREGION_CURRENCYatorganization-default-currency.ts:77, plugin-auth only; the console has no region→currency lookup). If the table cannot move to spec without a second hand-written copy, the thread stops and reports the fork before coding.Serial constraints cleared: none known.
objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUHdaZrLT9hey9hCyL
os-dev-report
{ "issue": 28872, "status": "done", "branch": "claude/project-thread-64s6kz", "pr": "https://github.com/objectstack-ai/objectstack/pull/28953", "session": "session_01QsR2jUHdaZrLT9hey9hCyL", "premise_still_valid": true, "summary": "Premise held: REGION_CURRENCY and currencyForLocale moved unchanged from plugin-auth into packages/spec/src/shared/region-currency.ts, exported from @objectstack/spec/shared (the entry the prompt logic already imports); plugin-auth imports the lookup from there and keeps no copy, and neither name was on the plugin-auth package entry. The console's one-time workspace prompt now also asks about localization.currency while it resolves from source default with no value, unlocked, for a session holding the namespace writePermission, offering currencyForLocale of the browser's FIRST language (zh-CN gives CNY, shown as 人民币(CNY) in zh). Both unset gives one dialog with both SettingsFields, one settings read and one PUT /api/settings/localization carrying the keys whose field holds a value; decline writes nothing; the existing per-device record covers both arms. No new settings key, server dismissed flag or gate. Eight new console.workspaceTimezonePrompt keys in all ten locales.", "tests": "spec vitest region-currency + value-domain: 2 files 41/41 (at 2c068e8f, spec src unchanged since). plugin-auth vitest organization-default-currency + organization-created-default-currency: 2 files 26/26 (at 2c068e8f, unchanged since). ui vitest three WorkspaceTimezonePrompt files + packages/i18n/: 97 files, 1477 passed, 13 skipped, 0 failed (head 93694e2e). ui vitest scripts/: 169 files passed, 2 skipped, 4900 tests (93694e2e). Ablation via ablation-replace.mjs on 'if (currency) offer.currency = currency;' in findWorkspacePromptOffer: currency file 11 failed / 9 passed; restore leg blob == HEAD bea81a1b8210, git diff HEAD empty. Source-resolved (relative import), no dist leg. One existing timezone pin edited: 'no browser zone at all -> no prompt and no request' now also stubs navigator language es-419, since a browser with a currency to offer now reads the settings.", "gates": "dispatch-gates --ran: 87 derived, 86 run, 1 NOT MEASURED (pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET, needs a full pnpm build; CI's Lint & Repo Gates runs it). check-plugin-teardown-shape --self-test exit 3 on the shallow clone's fixture commit, exit 0 after fetching 621a4876. Fast gates re-run on final head 93694e2e after the last commit (sdui-manifest, nul-bytes, doc-authoring, keyed-text-bounds, issue-citations, closed-ceremony-text, role-word, comment-mask-adoption, check-affected-docs, cross-package-test-inputs): all exit 0. PR-body gates on the exact body sent (changeset-no-major, closing-keyword-parity, partof-closing-keyword, adr-0087-registration, empty-changeset): exit 0. spec check:generated: api-surface/shared.json + export-origins/shared.json regenerated, then all up to date. turbo type-check console+i18n 72/72, typecheck spec+plugin-auth 33/33. eslint changed files exit 0. ui check:new-line-citations 0 new. PR check-runs: in_progress at report time.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "3 — get_session, issue_read get, issue_read get_comments (all reads)", "api_writes": "1 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft), read back 9256/9256 bytes identical; plus this os-dev-report comment", "open_questions": [], "out_of_scope_findings": [ "boundary, noted not filed: an administrator already shown the timezone prompt on a device before this lands is not asked about the currency there, because the card puts the currency arm under the prompt's existing record. carrier: none", "boundary, noted not filed: a browser whose first language has no country region (es-419, the card's own example of the gap) gets no currency offer and so no currency prompt, by the card's 'offering the browser region's currency'; asking with an empty picker would close it but is a different reading. carrier: none" ], "deviations": [ "Branch base 97680bbb; origin/main moved 7 commits by pr_create, none touching a file this diff changes; not merged before pr_create.", "No assignees or reviewers written on the PR (os-dev rule 2; harness asked for them, the repo contract wins)." ] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Claim amended · 2026-11-02T05:03Z · after Live E2E UI went red on PR #28953 (head
93694e2e, job 125101832890). This widens the file surface by one file. It adds to claim 6568772992. Branch, session, size and declarations are unchanged.workspace timezone already chosen; no prompt to answer. It decides whether to answer the prompt fromlocalization.timezoneonly (ui/e2e/live/global-setup.ts,answerTimezonePrompt). On the CI backend the timezone is chosen but the currency is not. So the PR's currency arm opens the modal on every app page. Nine specs time out on a click behind it (action-modal,list-row-action-cel,master-detail,record-form-direct-url,screen-flow).ui/e2e/live/global-setup.ts. The prompt check also asks aboutlocalization.currencywith the console's own condition (resolves from the default, not locked). The run then answers the one prompt through its own buttons, as it does for the timezone today.LIVE_TIMEZONE_PROMPTkeeps its meaning for the combined prompt.objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUHdaZrLT9hey9hCyL
os-dev-report
{"issue":28872,"round":"amendment 6569504288 (Live E2E UI red at 93694e2e)","status":"done","pr":"https://github.com/objectstack-ai/objectstack/pull/28953","head":"86224bf0","session":"session_01QsR2jUHdaZrLT9hey9hCyL", "summary":"ui/e2e/live/global-setup.ts answerTimezonePrompt: wouldAsk is now timezone (source default, unlocked) OR currency (source default, unlocked, no value), the console's own per-key condition. The one prompt is answered through its existing testids (workspace-timezone-prompt, -decline, -confirm, unchanged for the combined dialog); LIVE_TIMEZONE_PROMPT keeps decline/confirm meaning. Log lines reworded; nothing reads them.", "tests":"Local reproduction against start-backend.sh (app-showcase, --fresh, :4317) + vite preview (:5317): PUT timezone=Asia/Shanghai to match CI (timezone tenant, currency null/default). Control leg with the 93694e2e global-setup: logged 'workspace timezone already chosen', action-modal.spec failed with the dialog overlay intercepting pointer events (the CI symptom); restored, git diff HEAD empty. Fixed leg: test:e2e:live:ci --retries 0 = 16 passed, '[live-e2e] workspace prompt answered: decline'. LIVE_TIMEZONE_PROMPT=confirm leg: action-modal passed, backend then read currency USD (tenant), timezone unchanged.", "gates":"derived gate list unchanged by the edit; re-run nul-bytes, issue-citations, doc-authoring, comment-mask-adoption, ui new-line-citations: exit 0. eslint global-setup.ts: 0 errors (1 pre-existing no-explicit-any warning on an untouched line). CI at 86224bf0: Live E2E UI in_progress at report time; Lint/PR-body/Changeset/Governed guard success.", "deviations":["The PR body does not name global-setup.ts; its What changes could gain one line ('the live global setup answers the prompt when the currency is unset too'). Not rewritten by me (dev writes the body once)."]}objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Landed · 2026-11-02T05:53Z · PR #28953 merged through the merge queue as
2ac698baat 2026-11-02T05:53Z (GitHub's clock). ACCEPT 6569873693 at head86224bf0.2ac698ba~1)origin/main)export const REGION_CURRENCYinplugin-auth/src/organization-default-currency.tspackages/spec/src/shared/region-currency.tsexistsexport const CURRENCY_KEYinconsole/src/pages/settings/workspaceTimezonePromptLogic.tsFixes #28872closed this card.pm:dispatchedcomes off in the same stroke as this note.