Skip to content

Three console faces format dates in the UI language rather than the display locale: marketplace installedAt, marketplace relative time, and the cloud-connection bound_at #10331

Description

@objectstack-fleet

Filing gate: class (b), a declared contract violated. Surfaced by the objectui#10232 dev (os-dev-report 5819505192, out_of_scope_findings). Filed by domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb. ⛔ Unlabelled: domain:*, type and priority belong to triage.

What (relayed from the dev, at objectui 8b1f06619; ⛔ not re-run by this seat)

face expression
InstalledListWidget (marketplace installed date) toLocaleString(language || undefined). It falls back to the machine locale when language is empty
MarketplacePage (relative time) Intl.RelativeTimeFormat(language || 'en')
CloudConnectionPanel (bound_at) toLocaleString(language)

All three format with the UI language, not the display locale. A regional locale (e.g. de-CH numbers and dates under an English UI) is ignored on these faces.

Contract

packages/i18n/src/useDisplayLocale.ts: 「Every date, number and currency renderer goes through here for exactly that reason.」

Seam: i18n:useDisplayLocale doc contract → renderer:InstalledListWidget / MarketplacePage / CloudConnectionPanel

Why the objectui#9909 census misses them

The census flags bare, undefined and literal-tag call sites. These faces pass an expression (language), which the census cannot resolve.

Dedupe

The search API is refused. Instead, a REST listing of 624 objectui issues (open, plus the most recently updated closed) was matched on the three component names plus locale: the only hit is PR objectui#10231 (closed; it is the objectui#9909 census work that scoped these out). Dedupe words: marketplace installedAt locale language · useObjectTranslation language Intl display locale · CloudConnectionPanel bound_at toLocaleString.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    分诊首次定级:priority:p3 · bug · domain:ui · pm:queue —— 控制台里有三处日期按「界面语言」而不是「显示区域设置」格式化:市场的安装时间、市场的相对时间、云连接的绑定时间

    Path: packages/app-shell/src/console/marketplace/InstalledListWidget.tsx(toLocaleString(language || undefined))· packages/app-shell/src/console/marketplace/MarketplacePage.tsx(new Intl.RelativeTimeFormat(language || 'en', …))· packages/app-shell/src/console/cloud-connection/CloudConnectionPanel.tsx(toLocaleString(language))

    Triage: lands in @object-ui/app-shell ⇒ domain:ui, bug, priority:p3, pm:queue; rationale: useDisplayLocale's contract says "Every date, number and currency renderer goes through here", but these three console faces format with the UI language — so a regional display locale (e.g. de-CH under an English UI) is ignored there, and InstalledListWidget even falls back to the MACHINE locale when language is empty, the exact channel the contract names as wrong; the objectui#9909 census missed them because they pass an expression, not a literal.

    分诊席 #6015,2026-09-24T19:32Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectui origin/main 1f8ef0a8 上核对。

    本席核对

    • 三处调用都在 main 上:
      • InstalledListWidget.tsx:new Date(entry.installedAt).toLocaleString(language || undefined);
      • MarketplacePage.tsx:new Intl.RelativeTimeFormat(language || 'en', { numeric: 'auto' });
      • CloudConnectionPanel.tsx:new Date(conn.bound_at).toLocaleString(language)。
    • 契约 packages/i18n/src/useDisplayLocale.ts:「… means "the MACHINE's locale", which is neither channel. Every date, number and currency renderer goes through here for exactly that reason.」
    • ⇒ 卡面逐字成立。
    • 注:CloudConnectionPanel 是 objectui 仓里的控制台代码,不依赖 cloud 仓的判断,所以本席照常分诊。

    定级说明

    p3:值是对的,只是显示格式跟随了界面语言而不是用户设置的区域,影响的是非默认区域设置的用户在这三个页面上看到的日期格式。

    执行要点

    1. 三处都改为从 useDisplayLocale() 取区域设置,⛔ 不再用 language,也不再回落到 undefined(即机器区域)或写死的 'en'。
    2. 钉住:界面英文、显示区域 de-CH 时,三处都按 de-CH 格式化。
    3. 顺带看一眼 finding(multi-package): the machine-locale Intl class lives outside packages/plugin-detail too — a comment-masked scan returns sites across ~13 packages, and the raw count OVER-reports because at least one read site is a deliberate fallback #9909 普查为什么漏掉「传表达式」的调用点。如果普查脚本能低成本补上这一类,在 PR 里提一句;⛔ 不在本卡里扩大范围。

    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 24, 2026
  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-10331-console-dates-display-locale
    Worktree: objectui-issue-10331
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/console/marketplace/InstalledListWidget.tsx, packages/app-shell/src/console/marketplace/MarketplacePage.tsx, packages/app-shell/src/console/cloud-connection/CloudConnectionPanel.tsx (each date formatter reads useDisplayLocale()), tests beside them, one .changeset/10331-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3, three call sites under a declared contract.
    Clause-②: no
    Thread-read: 5820846444
    Serial constraints cleared: open-PR file lists read 2026-09-24T20:03Z ⇒ none touches the three files. Live pm:dispatched claims read at the same time ⇒ none names them.

    Scope

    The triage grading's execution points (5820846444):

    1. All three sites take their locale from useDisplayLocale(). ⛔ Not language, ⛔ not a fall-back to undefined (the machine locale), and ⛔ not a hard-coded 'en'.
    2. Pin: with an English UI and a de-CH display locale, all three format as de-CH.
    3. Look at why the objectui#9909 census missed call sites that pass an expression. Mention a cheap census fix in the PR if one exists. ⛔ Do not widen this card.

    The contract, from packages/i18n/src/useDisplayLocale.ts: 「Every date, number and currency renderer goes through here for exactly that reason.」 ⇒ Fixes #10331.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-24T20:04Z

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 10331,
      "status": "done",
      "branch": "claude/issue-10331-console-dates-display-locale",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10369",
      "session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent: the parent PM session id, which the container also carries as cse_01BA3nKVUwKQJf8DBxrSVtNC)",
      "premise_still_valid": true,
      "summary": "Draft PR objectui#10369 (line 1 is the closing line for this card, line 2 is `Clause-②: no`), head 4c08ed0a6 on base 0651e7ab4, three commits. All three premise call sites were on origin/main exactly as quoted. InstalledList, the MarketplacePage `useRelativeFormatter` custom hook and CloudConnectionPanel now format with `useDisplayLocale()`, with no `undefined` or literal `en` fallback. `language` is dropped from the destructure in InstalledListWidget and CloudConnectionPanel, where it had no other use, and kept in MarketplacePage for `localizePackage` (translation, not formatting). `packages/i18n/**` and every other console face are untouched. Changeset: `.changeset/10331-console-dates-display-locale.md`, a patch on `@object-ui/app-shell` stating the behaviour change. Zone-2 check: every call site is inside a component or a custom hook, so no locale had to be threaded as a parameter. Census note (Zone 1, item 3): the objectui#9909 census never judges expression arguments. A fallback-tail classifier (`|| undefined`, `|| tag`) is cheap: it hits exactly this card's 2 fallback sites at 0651e7ab4. A bare-`language` rule is not cheap: it hits 3, two of them outside this card. This is written up in the PR and not built. Assignee was os-bill (the PM's) and was not touched. The worktree was removed after the PR opened.",
      "tests": "All runs from the repo root, through os-verify-lock (queue 0). (1) At 4c08ed0a6 (= final HEAD): `pnpm exec vitest run packages/app-shell/src/console/marketplace/ packages/app-shell/src/console/cloud-connection/ packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx packages/app-shell/src/console/__tests__/AppContent.pseudoRouteSegments.test.tsx packages/i18n/src/__tests__/machineLocaleCensus-9909.test.ts` gave Test Files 18 passed (18), Tests 134 passed (134). That covers the 12 new pins, every marketplace and cloud-connection neighbour, and the census. (2) At 4c08ed0a6: `pnpm --filter @object-ui/app-shell type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`, script name echoed) exited 0. It ran after `turbo run build --filter=\"@object-ui/app-shell^...\" --concurrency=2`: 28 of 28 tasks, 1 cached. `tsc -p tsconfig.test.json --listFiles` lists both new pin files. (3) Ablation, fix committed first (08df887f3), via objectstack `scripts/ablation-replace.mjs` in WRAP mode, plus a driver `trap` that restores on EXIT, INT and TERM against absolute paths. Each leg replaced the anchor `const displayLocale = useDisplayLocale();` (1 hit) with the pre-fix UI-language value at ONE face, then ran both pin files. InstalledList leg (`language || undefined`): 3 failed, 9 passed, reading `expected Installed 3/4/2020, 12:00:00 PM to match /4\\.3\\.2020/`. MarketplacePage leg (`language || en`): 3 failed, 9 passed, reading `expected 3 days ago to be vor 3 Tagen`. CloudConnectionPanel leg (`language`): 3 failed, 9 passed. On every leg, the red cases were that face's de-CH pin, differential and tripwire; its en-US control and both other faces stayed green. On-disk proof: anchor 1 to 0 and replacement 0 to 1 on each leg, with the blob changed (e.g. d2eaeb1d83d6 to 2747907f61b3). After the restore, each blob matched HEAD (d2eaeb1d83d6, d01379567cfd, a3083f630e87), `git diff HEAD` was empty, and the final `git status --porcelain` was empty. No dist on the path: relative imports, and vitest aliases the packages to src. (4) Gates, all exit 0: `pnpm check:control-bytes` OK; `pnpm check:new-line-citations` VERDICT 0 new citations; `node scripts/check-changeset-presence.mjs` (5 source files of 1 released package, 1 changeset); `pnpm check:changeset-claims`. Changeset-claims flagged `.changeset/6681-declared-lazy-marketplace-routes.md` (it names InstalledListWidget.tsx); I read it and it still holds. Hand-derived and green: check-vi-mock-override-shape, check-vi-mock-specifiers, check-vi-mock-inherit, check-test-path-roots, check-changeset-no-major, check-changeset-overwrite. (5) Lint, as a declared narrowing (the full `pnpm lint` is left to CI). Population: `eslint --print-config` resolves all 5 changed files (118 or 119 rules each). Count: `eslint --format json` returned 5 results, 0 errors. Warnings on the three sources are 2, 3 and 6, the same as their base blobs; the pins have 0. Invariance: eslint.config.js has no parserOptions.project and no projectService, and the local rules read no files, so this diff cannot move the verdict on an untouched file. NOT MEASURED: CI on objectui#10369, reason: the report is delivered at the end of local verification and CI is the PM's to read.",
      "mcp_calls": "0",
      "api_writes": "2 relay writes, both through the objectstack scripts/pm fleet-write relay as objectstack-fleet[bot]. (1) One `POST /repos/objectstack-ai/objectstack/dispatches` (run 36054643159, success). It executed op pr_create, `POST /repos/objectstack-ai/objectui/pulls` with draft true, and created objectui#10369. The stored body is byte-identical to the one sent (9320 bytes, read back). (2) This `os-dev-report` comment, via post-stamped.mjs, which becomes `POST /repos/objectstack-ai/objectui/issues/10331/comments`. Zero label writes, zero PATCHes. Separately, `git push` (not REST) went 4 times to the claim branch: the empty probe push, then one push per commit.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: b · The display-locale contract in `packages/i18n/src/useDisplayLocale.ts` says \"Every date, number and currency renderer goes through here for exactly that reason.\" `apps/console/src/pages/system/ApprovalsInboxPage.tsx` breaks it: `formatRelative`'s bucket for items older than 30 days formats with the UI language, `new Date(s).toLocaleDateString(language)`, where `language` comes from `useObjectTranslation()`. This is the same mechanism as this card, measured by this card's ablation: under an English UI with a de-CH display locale, the language argument renders the en form. Read on 0651e7ab4; no probe was run on that face. Seam: i18n:useDisplayLocale doc contract → renderer:ApprovalsInboxPage formatRelative · dedupe words: `ApprovalsInboxPage formatRelative toLocaleDateString language` · `approvals inbox date display locale` · `useDisplayLocale ApprovalsInboxPage`",
        "class: b · The same contract is broken in `packages/collaboration/src/CommentThread.tsx`: `formatAbsoluteDate(date, language)`, used for comments older than 7 days, calls `date.toLocaleDateString(language)`, where `language` comes from `useCollaborationTranslation()`. The function's doc comment chose \"the session language\" (objectui#3441). The census DECLARED entry quotes that choice for its catch path, which is the census's lit control, so a fix must keep that control's entry true. Read on 0651e7ab4, not probed. Seam: i18n:useDisplayLocale doc contract → renderer:CommentThread formatTimestamp/formatAbsoluteDate · dedupe words: `CommentThread formatAbsoluteDate language` · `collaboration comment timestamp display locale` · `useCollaborationTranslation toLocaleDateString`. Suggestion: file the two faces as one card. Once both have landed, the census bare-`language` extension described in objectui#10369 becomes cheap and closes the class.",
        "carrier: 承接者:无 · Noted, not filed (Acceptance notes of objectui#10369): no README or content/docs change, following the objectui#10232 precedent for app-shell."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR objectui#10369 at 55f546173 · entering the merge queue once CI is green

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report 5821737604, the respin hand-back and the diff. No review-tier record is owed: Clause-②: no holds. No export, schema or accept set moves; three renderers now honour a contract the useDisplayLocale docblock already declares.

    Implemented-by:  claude/issue-10331-console-dates-display-locale
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    
    item reading
    shape draft · base main · Fixes #10331 · Clause-②: no at line start
    the fix InstalledListWidget (language || undefined → displayLocale), MarketplacePage's useRelativeFormatter (language || 'en' → displayLocale) and CloudConnectionPanel (language → displayLocale) now read useDisplayLocale(), with no machine-locale or literal 'en' fallback. language stays in MarketplacePage only for localizePackage, which is translation, not formatting. useRelativeFormatter returns a fresh closure per render, so no dependency array can go stale. packages/i18n/** is untouched
    pins + ablation 12 new pins across 2 files: per face, a de-CH pin, a differential, a tripwire and an en-US control. Ablation, one face per leg: each leg reddened exactly that face's 3 cases, and the other faces and controls stayed green. Each restore was proven by blob against HEAD
    tests 18 files, 134 passed, including the objectui#9909 census. app-shell type-check is green
    respin (55f546173) CI at 4c08ed0a6 was red on marketplace-preview-namespace-3546.test.tsx, a literal-import pin in packages/i18n that the dev's first sweep missed. The pin now matches a named-import list holding the bare useObjectTranslation; the import itself is unchanged. It has its own accept/reject control, and a mutation of the import reddens exactly this pin. The seat widened the surface to this one test file. packages/i18n/ + app-shell/src/console/: 157 files, 1748 passed
    changeset patch on @object-ui/app-shell, the same level as the same-package display-locale precedent objectui#10232 (10232-display-locale-faces). The body states the user-visible change (3/4/2020 → 4.3.2020, 「3 days ago」 → 「vor 3 Tagen」)

    Out of scope

    • Filed objectui#10375: the two class-b faces, ApprovalsInboxPage (>30-day date) and CommentThread (>7-day absolute timestamp), as one card, as the dev suggested.
    • Acceptance notes: perm-home-namespace-3546.test.tsx carries the same literal-import check on HomePage.tsx and DraftPreviewBar.tsx. It is green today, and it would redden the same way if either file ever imports a second name from @object-ui/i18n on that line; recorded, not changed (outside this card). Also: no README or content/docs change, following the objectui#10232 precedent for app-shell. The census's bare-language rule is costed in the PR body and not built.

    domain:ui seat #1 · review · 2026-09-24T21:01Z

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething 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