You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bug(plugin-grid): the column-summary footer formats four of its five arms in the MACHINE locale, so the footer and the cells above it can read under two conventions #9294
Found by the os-dev seat while implementing objectui#9269 (PR #9293). Filed unassigned and ungraded — no domain:*, no priority, no pm:*; that is triage's call.
⛔ Deliberately NOT folded into #9293. That card's landing point is "one surface re-spells the declared percent source"; this is a different mechanism — which locale the footer formats in, not which source owns the percent rule — and it reaches four arms the percent card has no mandate over. #9293 says so in code and names this card's subject in a scope comment.
The claim
formatSummaryLabel in packages/plugin-grid/src/useColumnSummary.ts formats four of its five arms with no locale argument at all:
// currency armnewIntl.NumberFormat(undefined,{style: 'currency', currency, ... })// percent-of-rows armvalue.toLocaleString(undefined,{maximumFractionDigits: 1})// avg armvalue.toLocaleString(undefined,{maximumFractionDigits: 2})// the count / non-numeric arm and the numeric defaultvalue.toLocaleString()
An omitted or undefined locale means the machine's locale, which is neither of this renderer's two locale channels. useDisplayLocale's own doc comment states the contract and names this exact mistake:
The one thing a caller must not do is reach past this hook for the raw tenant locale and hand Intl the undefined it gets on an unconfigured workspace — undefined means "the MACHINE's locale", which is neither channel. Every date, number and currency renderer goes through here for exactly that reason.
The cells this footer sits under do go through it: ObjectGrid.tsx reads useDisplayLocale() and threads it (its mobile-card percent face passes it to formatPercent). So the footer and the column above it can be formatted under two different locales in the same viewport — the harm class objectui#4576 measured and paid for, one axis over.
Measured, not inspected
Rendered through the hook itself (renderHook, I18nProvider at en so only the TENANT locale moves, LocalizationProvider carrying the locale, one row, EUR). The machine locale in the measuring container is en-US:
tenant locale
currency sum
plain sum
avg
percent_filled
percent-column sum
en
Sum: €1,235
Sum: 1,234.5
Avg: 1,234.5
Filled: 100%
Sum: 25%
de-DE
Sum: €1,235
Sum: 1,234.5
Avg: 1,234.5
Filled: 100%
Sum: 25 %
tr-TR
Sum: €1,235
Sum: 1,234.5
Avg: 1,234.5
Filled: 100%
Sum: %25
⭐ Read the rows, not the cells. The first four columns do not move at all when the tenant locale moves — that is the defect, stated as a measurement rather than as a reading of the source. The last column is the one PR #9293 repaired, and it is the control that proves the harness really does move the locale: if the wrapper were inert, that column would not move either.
A de-DE session should read 1.235,00 € and 1.234,5; a tr-TR session 1.235,00 € and 1.234,5. What it gets is the measuring machine's en-US in both, and in a browser it would get whatever that browser happens to be set to — which is invisible in review and non-deterministic in CI, the two reasons useDisplayLocale returns a concrete 'en' rather than undefined.
⛔ Not objectui#4589's surface. That card moved the number-display POLICY into one home; every call here still reaches that home, just with the wrong locale handed to it.
⛔ Not a claim about which formatter each arm should use. formatCurrency / formatNumber in @object-ui/fields are the obvious candidates and they take a locale as their third positional parameter, but choosing between "thread the tag into the existing Intl calls" and "route the arms through the shared formatters" is a decision this note does not make. The second is a wider change and may have its own rendered-output consequences that need measuring first.
Why it needs a decision and not just a patch
Every fix here moves rendered output in every non-en session — the same property that made #9293 a minor with a breaking paragraph. The blast radius is wider than #9293's: it is four arms including currency, where a moved thousands separator is read as a moved decimal point in exactly the locales that swap them.
Dedup
Channel: repo-scoped REST issues list, state=open, per_page=100, followed by page number to a short page (pages 1-4 returned 100, page 5 returned 67), then a local title-and-body regex scan. Total enumerated 467 rows = 451 issues + 16 pull requests, cross-checked against this repository's open_issues_count of 467 — the enumeration is complete for the open set. /search/issues was not used: this container's egress proxy refuses it by path whitelist.
Controls fired both ways in the same scan: percentDisplayValue returned 3 (objectui#9269, objectui#9167, objectui#5560 — all readable directly), and an impossible token returned 0.
Scans: useDisplayLocale returned 1 (objectui#4738, which is address PART ORDER following the reader's locale — the opposite complaint, and not about numbers); toLocaleString returned 3 (this card's parent objectui#9269, objectui#8209 on readonly datetime WIDGET faces in @object-ui/fields, objectui#7174 on five formatTimestamp near-copies); useColumnSummary returned 3 (objectui#9269, objectui#8203 on narrowed-t type declarations, objectui#2890 on ListView spec vocabulary); a summary-near-locale scan returned only objectui#9269. None names this footer's locale.
⛔ Boundary, stated rather than papered over: open issues only. No text query over CLOSED cards was possible in this session — /search/issues is proxy-refused and the MCP search tool is rate-limited on its own identity — so a closed card covering this would not have been seen.
Written by an agent seat during objectui#9269; attribution is prose here deliberately, because a footer block is stripped when an issue is created.
Found by the
os-devseat while implementing objectui#9269 (PR #9293). Filed unassigned and ungraded — nodomain:*, no priority, nopm:*; that is triage's call.⛔ Deliberately NOT folded into #9293. That card's landing point is "one surface re-spells the declared percent source"; this is a different mechanism — which locale the footer formats in, not which source owns the percent rule — and it reaches four arms the percent card has no mandate over. #9293 says so in code and names this card's subject in a scope comment.
The claim
formatSummaryLabelinpackages/plugin-grid/src/useColumnSummary.tsformats four of its five arms with no locale argument at all:An omitted or
undefinedlocale means the machine's locale, which is neither of this renderer's two locale channels.useDisplayLocale's own doc comment states the contract and names this exact mistake:The cells this footer sits under do go through it:
ObjectGrid.tsxreadsuseDisplayLocale()and threads it (its mobile-card percent face passes it toformatPercent). So the footer and the column above it can be formatted under two different locales in the same viewport — the harm class objectui#4576 measured and paid for, one axis over.Measured, not inspected
Rendered through the hook itself (
renderHook,I18nProvideratenso only the TENANT locale moves,LocalizationProvidercarrying the locale, one row,EUR). The machine locale in the measuring container isen-US:sumsumavgpercent_filledsumenSum: €1,235Sum: 1,234.5Avg: 1,234.5Filled: 100%Sum: 25%de-DESum: €1,235Sum: 1,234.5Avg: 1,234.5Filled: 100%Sum: 25 %tr-TRSum: €1,235Sum: 1,234.5Avg: 1,234.5Filled: 100%Sum: %25⭐ Read the rows, not the cells. The first four columns do not move at all when the tenant locale moves — that is the defect, stated as a measurement rather than as a reading of the source. The last column is the one PR #9293 repaired, and it is the control that proves the harness really does move the locale: if the wrapper were inert, that column would not move either.
A
de-DEsession should read1.235,00 €and1.234,5; atr-TRsession1.235,00 €and1.234,5. What it gets is the measuring machine'sen-USin both, and in a browser it would get whatever that browser happens to be set to — which is invisible in review and non-deterministic in CI, the two reasonsuseDisplayLocalereturns a concrete'en'rather thanundefined.What it is not
colType === 'percent'arm only, and it is already green above.formatCurrency/formatNumberin@object-ui/fieldsare the obvious candidates and they take a locale as their third positional parameter, but choosing between "thread the tag into the existingIntlcalls" and "route the arms through the shared formatters" is a decision this note does not make. The second is a wider change and may have its own rendered-output consequences that need measuring first.Why it needs a decision and not just a patch
Every fix here moves rendered output in every non-
ensession — the same property that made #9293 aminorwith a breaking paragraph. The blast radius is wider than #9293's: it is four arms including currency, where a moved thousands separator is read as a moved decimal point in exactly the locales that swap them.Dedup
Channel: repo-scoped REST issues list,
state=open,per_page=100, followed by page number to a short page (pages 1-4 returned 100, page 5 returned 67), then a local title-and-body regex scan. Total enumerated 467 rows = 451 issues + 16 pull requests, cross-checked against this repository'sopen_issues_countof 467 — the enumeration is complete for the open set./search/issueswas not used: this container's egress proxy refuses it by path whitelist.Controls fired both ways in the same scan:
percentDisplayValuereturned 3 (objectui#9269, objectui#9167, objectui#5560 — all readable directly), and an impossible token returned 0.Scans:
useDisplayLocalereturned 1 (objectui#4738, which is address PART ORDER following the reader's locale — the opposite complaint, and not about numbers);toLocaleStringreturned 3 (this card's parent objectui#9269, objectui#8209 on readonlydatetimeWIDGET faces in@object-ui/fields, objectui#7174 on fiveformatTimestampnear-copies);useColumnSummaryreturned 3 (objectui#9269, objectui#8203 on narrowed-ttype declarations, objectui#2890 on ListView spec vocabulary); asummary-near-localescan returned only objectui#9269. None names this footer's locale.⛔ Boundary, stated rather than papered over: open issues only. No text query over CLOSED cards was possible in this session —
/search/issuesis proxy-refused and the MCP search tool is rate-limited on its own identity — so a closed card covering this would not have been seen.Written by an agent seat during objectui#9269; attribution is prose here deliberately, because a footer block is stripped when an issue is created.
Generated by Claude Code