Skip to content

Console: when the organization has no default currency, the workspace prompt that asks for the timezone also offers a currency — today amounts silently show as bare numbers #28872

Closed

Description

@objectstack-fleet

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).

  1. The prompt also covers the currency.
    • Condition: localization.currency resolves 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.
  2. When both the timezone and the currency are unset, it is one prompt, not two dialogs.
  3. 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.currency is 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

  1. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:ui · 2026-11-02T02:59Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 session session_01QsR2jUJ19MstFvFhjgiYTu
    Account: zhuangjianguo
    Branch: claude/project-thread-64s6kz (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-28872
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: a new packages/spec/src module 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.tsx and workspaceTimezonePromptLogic.ts; the console.workspaceTimezonePrompt.* keys in ui/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: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6567982412
    Premise: to verify first, per the card (at origin/main 97680bbb: the table is REGION_CURRENCY at organization-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.

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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)."
      ]
    }
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • Measured: the live run's global setup logged workspace timezone already chosen; no prompt to answer. It decides whether to answer the prompt from localization.timezone only (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).
    • File surface (added): ui/e2e/live/global-setup.ts. The prompt check also asks about localization.currency with 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_PROMPT keeps its meaning for the combined prompt.
    • Declarations: unchanged.
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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)."]}
  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

    Landed · 2026-11-02T05:53Z · PR #28953 merged through the merge queue as 2ac698ba at 2026-11-02T05:53Z (GitHub's clock). ACCEPT 6569873693 at head 86224bf0.

    probe before (2ac698ba~1) now (origin/main)
    export const REGION_CURRENCY in plugin-auth/src/organization-default-currency.ts 1 0
    packages/spec/src/shared/region-currency.ts exists no yes
    export const CURRENCY_KEY in console/src/pages/settings/workspaceTimezonePromptLogic.ts 0 1

    Fixes #28872 closed this card. pm:dispatched comes off in the same stroke as this note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions