Skip to content

feat(ui): record-mode currency — each record shows its own currency; ISO 4217 code select pre-filled with the organization currency; no cross-currency totals; Studio currency section - #27340

Merged
objectstack-fleet[bot] merged 31 commits into
mainfrom
claude/project-thread-irhbgx
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 31 commits into
mainfrom
claude/project-thread-irhbgx

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5

Requested by Bill · project thread

Fixes #27188

⛔ not released alone — R1: pairs with #27184, #27186, #27187, #27188 (one Version Packages release)
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: record 模式的金额按每条记录自己的币种显示(列表、表单、详情、仪表盘、甘特提示);币种码字段变成 ISO 4217 分组搜索下拉,「可用」组含组织币种和该对象已用过的币种,新建时预填组织币种;列表筛选里 record 金额条件带必填币种选择,生成「金额条件 且 币种 = C」;列表页脚与非按币种分组的合计不再跨币种相加;Studio 字段编辑器新增币种模式、币种字段选择和「创建币种字段」;分享链接页的 record 金额也按记录自己的币种显示;按币种分组的分析结果(仪表盘表格、报表表格及其导出、Studio 数据集预览)每行按该行币种格式化。
  • 为什么改: 没有这一步,record 字段会显示租户币种符号,比今天更错(ADR-0144 D9:R1 必含)。
  • 风险与回滚: 只影响声明 currencyMode: 'record' 的字段和声明 ISO 4217 值域的文本字段;dynamic / fixed 字段行为不变(原有测试全过)。回滚即 revert 本 PR。
  • 先看: ui/packages/i18n/src/currency.ts(解析器,record 分支)、ui/packages/components/src/custom/filterRecordCurrency.ts(筛选配对)、ui/packages/components/src/renderers/form/usedCurrencyCodes.ts(已用币种查询)、ui/packages/plugin-grid/src/recordCurrencyAggregates.ts(合计规则)。
  • 两分钟验证: cd ui && pnpm exec vitest run packages/fields/src/__tests__/recordCurrency.faces-27188.test.tsx,应见三行 $ / € / ¥,无币种的行显示纯数字。
  • 需要你做的: 无,席位落地(feat(spec): currencyMode: 'record' + currencyField — a record-mode amount points at a sibling ISO 4217 code field (multi-currency M0-1) #27268 已合并)。

What changes

ADR-0144 D2 (the record currency lives in a sibling code field), D6 (raw aggregates over a record-mode amount stand only inside one currency) and D8 rule 4 (the form pre-fills the organization currency; the stored value is always explicit), on the renderer side.

  • Resolver (@object-ui/i18n). resolveFieldCurrency(field, tenantDefault, record?) gains an optional record. Under currencyMode: 'record' it reads only record[currencyConfig.currencyField]; no record or a blank code resolves to undefined, so the face shows a plain number. It never falls back to the tenant default, a node-level currency or a stray defaultCurrency. dynamic / fixed are unchanged. New recordCurrencyField(field) names the pointer; FieldCurrencyConfig types the three modes.
  • Faces that hold the record now pass it. CurrencyCellRenderer and the formula cell (the grid already hands dependentValues={row}), CurrencyField (symbol, minor-unit decimals and spinner step follow the selected code), the form renderer (hands the live record to the currency and formula widgets, RECORD_CURRENCY_WIDGET_TYPES), the grid's mobile card, the record page's summary chips, the dashboard table cell and record drawer (FieldMeta.currencyConfig, copied only for record mode), and Gantt tooltips.
  • Code field. A text field declaring valueDomain: 'iso_4217_currency' renders CurrencyCodeField: a searchable select grouped "Available" (home currency, then rated, then the codes in use) and "All currencies (ISO 4217)", offering only codes isValueDomainMember('iso_4217_currency', code) accepts. Re-selecting the selected code keeps it (no click-to-clear). The picker is CurrencyCodePicker in @object-ui/components, shared with the filter row below.
  • Used codes (@object-ui/components form renderer, usedCurrencyCodes.ts). For every ISO 4217 code field on a form with an objectName, the renderer asks dataSource.queryGroupHeaders(object, compileListViewGroupQuery(grouped by the code field)) once, keyed on content, and hands the distinct stored codes down on the field's own metadata as usedCurrencyCodes. No widget-contract key. All five plugin-form hosts already put objectName on the form schema, so this one seam covers them. A data source without queryGroupHeaders is not asked (aggregate may degrade to a full fetch); a failed read hands nothing down. Either way the picker offers home + rated + the record's own code.
  • Filters (@object-ui/components FilterBuilder, @object-ui/plugin-list). A builder field entry may carry currencyField (the list sets it from currencyConfig for record-mode amounts). A value-comparing row on such an amount draws a required currency picker, defaulting to localization.currency. The builder emits the code condition as a companion row of the same flat group, { id: '<row id>::currency', field: <code field>, operator: 'equals', value: C }, so the group lowers to amount op X AND code = C everywhere it is lowered (convertFilterGroupToAST gives ['and', ['amount','>',100], ['currency_code','=','USD']]) and stored filters keep the ViewFilterRule shape. The companion is drawn as the picker, never as a row, and goes when its amount row goes, switches to a non-pairing field, or switches to a presence operator. A paired group cannot be toggled to OR, because under OR the pair would split; a stored OR group shows why. Opening a stored filter emits nothing (a stored amount row without a code shows a blank, invalid picker until the next edit). The toolbar's filter count does not count companions.
  • Create forms (@object-ui/plugin-form). SeedContext.homeCurrency: a code field that some record-mode amount points at, with no declared default, opens on localization.currency. Wired into ObjectForm, DrawerForm, ModalForm, TabbedForm and WizardForm (SplitForm is retired on main, ui(plugin-form): ObjectForm drops the split / drawer / modal branches and SplitForm retires; showcase task views re-spelled (ADR-0050 cutover, renderer half of #27155) #27159). Edit forms are not seeded, so opening a record never rewrites its stored code.
  • Totals (@object-ui/plugin-grid). The footer draws no sum / avg / min / max for a record-mode amount; counts stand. Group-header aggregations keep those only when the grid is grouped by the amount's code field (honestGroupAggregations, applied before both the server header query and client bucketing). The home field is a dynamic amount and totals as before.
  • Studio (@object-ui/app-shell). A currency field, or a formula returning currency, gets a currency section (CurrencyConfigField): mode, the fixed code, and under record a currencyField picker listing only the object's text + ISO 4217 fields, with "Create currency field", which appends currency_code (type: 'text', valueDomain: 'iso_4217_currency', required: true) and sets the pointer in one write. A mode switch keeps only the keys that mode reads.
  • Share links (@objectstack/types, @object-ui/console; R1 rider from feat(spec): currencyMode: 'record' + currencyField — a record-mode amount points at a sibling ISO 4217 code field (multi-currency M0-1) #27268's contract review). The resolve answer's field projection (buildShareLinkResolveAnswer) carried only currencyMode and defaultCurrency, so a shared record-mode amount rendered as a plain number. A currency field's projected currencyConfig now also carries currencyField, only when the answer's record carries that code field, so a link that redacts the code field discloses neither the code nor the field's name and the amount shows as a plain number. The shared-record page passes the shared record to the currency face as dependentValues, so the amount shows in its own record's currency.
  • Dataset answers (@object-ui/core, @object-ui/plugin-dashboard, @object-ui/plugin-report, @object-ui/app-shell; R1 rider from feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338's contract review). A grouped analytics answer over a record-mode amount marks its measure column with AnalyticsResult.fields[].currencyField, naming the result column that holds each row's code (feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338). The readers passed only fields[].currency, so those columns rendered as plain numbers. New measureCellCurrency(field, row) returns that row's code when the column carries currencyField, nothing for a row without one, and the column's declared currency otherwise, so every other column formats byte for byte as before. It reads currencyField structurally ((field as { currencyField?: unknown }).currencyField): feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338's contract is not on main yet, so the spec type does not carry the key. That passes type-check, and it can switch to the spec type once feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338 lands. The dashboard table widget's body cells, the report table's body cells and its export snapshot, and the Studio dataset preview read it off the RAW row (state.rows[i], before relabelDimensions). Totals and cross-tab cells pass no row and so draw no symbol, since they may add several currencies. chart-series.ts reads no currency at all (only the numeral format), so it has nothing to switch, and a chart axis has one unit.
  • i18n. fields.currencyCode.* in all eleven locale packs; designer.field.currency.* in the studio namespace (en, zh).

File surface beyond the Claim's list, accepted by the epic PM: ui/packages/plugin-grid, ui/packages/components; also ui/packages/plugin-form, ui/packages/plugin-detail, ui/packages/plugin-gantt (one resolver call each, plus the form seeding), and ui/packages/plugin-list (the list filter's currencyField and filter count).

Tests

  • i18n/src/__tests__/resolveFieldCurrency.recordMode-27188.test.ts — the record-mode truth table.
  • fields/src/__tests__/recordCurrency.faces-27188.test.tsx — cells in USD / EUR / JPY, no-code rows plain, the amount box's symbol and step (USD 0.01, JPY 1, KWD 0.001), the code select's groups and domain filter, TextField routing.
  • plugin-form/src/__tests__/recordCurrencySeed-27188.test.ts — seeding, unset setting, author default and caller record win.
  • plugin-grid/src/__tests__/recordCurrencyAggregates-27188.test.tsx — footer refuses money aggregates over record mode, counts stand, home field sums; group aggregations by grouping field.
  • plugin-dashboard/src/__tests__/recordFields.recordCurrency-27188.test.tsx — dashboard cells per row.
  • app-shell/.../CurrencyConfigField.recordMode-27188.test.tsx — mode switch, candidates, and the create shortcut's output parses under ObjectSchema.
  • components/src/renderers/form/__tests__/form-used-currency-codes-27188.test.tsx — the query shape, the distinct domain-valid read, one ask per object across re-renders, no ask without an object or the capability, a failed read.
  • components/src/__tests__/filter-builder-record-currency-27188.test.tsx — the pairing rules, one picker per paired row, a new row on the home currency, removal, the stored blank picker emitting nothing, the AND lock and the OR notice.
  • plugin-list/src/__tests__/convertFilterGroupToAST.recordCurrency-27188.test.ts — the pair lowers to amount op X AND code = C.
  • packages/types/src/share-link-resolve-answer.test.ts gains: the pointer is projected when the record carries the code field, and a redacted code field is never named.
  • console/src/pages/SharedRecordPage.recordCurrency-27188.test.tsx — a shared record-mode amount shows its record's currency (EUR, then JPY), and a hidden code field leaves a plain number.
  • core/src/utils/__tests__/dataset-format.recordCurrency-27188.test.ts — measureCellCurrency: per-row code, no code / no row gives no symbol, a column without currencyField keeps currency; two rows of one column format as $1,234.50 and €1,234.50.
  • app-shell/.../previews/DatasetPreview.measureLocale.test.tsx gains: a record-mode column renders $1,234.50 and €99.00 in one table.
  • plugin-dashboard/src/__tests__/DatasetWidget.recordCurrency-27188.test.tsx — the table widget formats USD, EUR and a code-less row (plain), and the grand total has no symbol.
  • plugin-report/src/__tests__/DatasetReportRenderer.measureLocale.test.tsx gains: the grouped report table formats USD, EUR and a code-less row (plain), and the grand total has no symbol.
  • fields/.../recordCurrency.faces-27188.test.tsx gains: the used codes off field.usedCurrencyCodes appear in "Available" after the home currency.

Acceptance notes

Verification

Head 554bd578 on a9b2b97d, which regenerates packages/spec/authorable-surface/data.json on the merged tree (the result is byte-identical to main's), on 24055cf0, a merge commit bringing in main at f7188b56 after #27268 landed as 41452103 (no rebase). The diff against main is this card's own: 70 files, none under packages/spec. Two conflicts:

554bd578 moves measureCellCurrency above formatDimensionValue's docblock, which it had orphaned. It is a doc-only move. check:generated (spec) passes at this head. ADR-0144 citations (D2, D3, D6, D8 rules 3 and 4, D9) were re-read against the landed docs/adr/0144-…md and hold.

Landing verification at 554bd578
  • Builds: @objectstack/spec, @objectstack/types, and the ui closure of app-shell, console and plugin-report, exit 0. @objectstack/spec check:generated exit 0.
  • Tests, all exit 0 under os-verify-lock:
  • Type-check exit 0: @object-ui/ i18n, fields, components, plugin-form, plugin-grid, plugin-list, plugin-dashboard, plugin-detail, plugin-gantt, plugin-report, core, app-shell, console, and @objectstack/types.
  • Gates: dispatch-gates --commands on this card's 70 paths derives no family the earlier runs had not. The 34 local re-runs exit 0; the rest are CI's.
Targeted tests — 49 files, 786 tests, exit 0
cd ui && pnpm exec vitest run --maxWorkers=2 (every *27188* test above, #27268's resolveFieldCurrency suites, every components filter-builder suite, form-data-source-wiring, form-dependent-values, the plugin-list convertFilterGroupToAST suites, filter-operator-ast-parity and filter-count suites, and the currency neighbours: currencyMode.faces-10422, CurrencyField.minorUnits, TextField.maxLength-10179, optionDefaultSeed-11914, useColumnSummary.currencyMode-10422, recordFields.currencyConfig-10463, ObjectFieldInspector.valueDomain-7597)
 Test Files  49 passed (49)
      Tests  786 passed (786)
os-verify-lock: VERDICT command-exit 0

Before the merge, the neighbouring-package run (i18n, components form renderer and custom, fields widgets, plugin-list filter suites) was 328 files, 3544 passed, 30 skipped.

Type-check and lint

pnpm --filter @object-ui/PKG type-check exit 0 at ad386034 for i18n, fields, components, plugin-form, plugin-grid, plugin-list, plugin-dashboard, plugin-detail, plugin-gantt, app-shell, after building their dependency closure. pnpm exec eslint over every ui file this card touches: 0 errors.

Ablations at ad386034 — three, each red then restored
  • Resolver record branch, ui/packages/i18n/src/currency.ts: currencyMode === 'record' → 'ABLATED' (blob 82ae7ac6 → c78a363b): 7 failed / 8 passed across the i18n record-mode suite and the fields faces suite.
  • Used codes hand-off, ui/packages/components/src/renderers/form/form.tsx: withUsedCurrencyCodes(field.field || field, …) → field.field || field (blob 12d5c6b5 → 18cbb6d6): 1 failed / 6 passed.
  • Filter pairing, ui/packages/components/src/custom/filterRecordCurrency.ts: pairedCurrencyField answers undefined (blob 025b1560 → d78defb9): 9 failed / 4 passed across the builder suite and the AST lowering.

Each restored by ablation-replace.mjs: blob == HEAD, git diff HEAD empty.

Share-link rider at 8bcaa1bc
  • Tests: @objectstack/runtime share-links-field-projection.test.ts 12 passed; @objectstack/types share-link-resolve-answer.test.ts 9 passed; console SharedRecordPage.recordCurrency-27188, SharedRecordPage-27121, shared-record-shape 22 passed. All exit 0 under os-verify-lock.
  • Type-check: @objectstack/types and @object-ui/console exit 0. eslint over the five files: 0 errors.
  • Ablation, console page: dependentValues={record} → dependentValues={undefined} in SharedRecordFields.tsx (blob e4ce54ff → 88b99e73): 2 failed / 1 passed. Restored: blob == HEAD.
  • Ablation, projection: the currencyField spread → false ? … : {} in share-link-resolve-answer.ts (blob 8c7a3bd5 → 57379712): 1 failed / 8 passed. Restored: blob == HEAD.
CI fixes at 66a421cc and c36aa49e

CI's Test UI at 8bcaa1bc failed one test: ui/packages/types/src/__tests__/filter-builder-condition-id-8415.test.ts reads removeCondition's id filter verbatim, and this card had widened it to also drop the companion. The line is back to the pinned form. The companion still goes with its amount row, because handleChange runs syncRecordCurrencyRows, which drops a companion whose amount row is gone. The "removing the amount row removes its code condition" case covers this.

CI's Test UI at 8bcaa1bc also failed ui/examples/schema-catalog/test/form-field-channel-census-12130.test.tsx, which measures every key the form-field channel reads by name. This card adds two reads: the form's used-codes hand-off (usedCurrencyCodes.ts) reads the entry's field stash and valueDomain, and the currency-code widget reads valueDomain off its carrier. usedCurrencyCodes.ts joins the field row's readers. valueDomain gets a RUNTIME-ONLY ledger row: it is object-field metadata (ADR-0144 D2) the code field takes from the object, so it is ledgered rather than declared on FormFieldSchema.

  • Tests: the census and its effects file, 2 files, 120 passed, exit 0 under os-verify-lock.
Studio and #27268's record-mode refusals

#27268 refuses defaultCurrency under currencyMode: 'record', and currencyField has .min(1).

The Studio currency section never writes either refused shape. switchCurrencyMode drops defaultCurrency on the switch to record, the pointer picker deletes the key rather than writing '', and "Create currency field" writes a fresh { currencyMode: 'record', currencyField }. A new test drives the whole path through the inspector: a fixed EUR amount, a switch to Record, then a picked pointer. It asserts the saved config and parses the saved object with ObjectSchema.

  • Ablation: switchCurrencyMode carries prev.defaultCurrency into record (blob ad4d29d0 → 8cd4f3de): 2 failed / 3 passed. Restored: blob == HEAD.
  • Tests: the targeted ui set plus the Studio, census and fix(spec): alias visible/showWhen onto visibleWhen on page:tabs items and screen fields #8415 files, 51 files, 831 passed. Spec currency-config-record-mode.test.ts: 15 passed. Share-link runtime, types and console: 12, 9 and 22 passed. All exit 0 under os-verify-lock.
  • Type-check: i18n, fields, components, plugin-list, app-shell and console exit 0.
  • Gates: the 27 local re-runs, plus the 8 families dispatch-gates --commands newly derives at this head (check:adr-anchors, bash32-floor, cli-command-ids, entry-guard, parse-guard, pnpm-filter-targets, pnpm-flag-spellings, scripts-symbol-anchors), exit 0 each.
Dataset-answer rider at 7d062908
  • Ablation: measureCellCurrency always answers the column's currency (blob 488643d0 → da6fa0c1): 4 failed / 9 passed, one red case in each of the three files (core, preview, dashboard table). Restored: blob == HEAD.
  • Tests: every suite under core/src/utils/__tests__/, app-shell/.../metadata-admin/previews/ and plugin-dashboard/src/__tests__/: 433 files, 5275 passed, 6 skipped, exit 0 under os-verify-lock.
  • Type-check: @object-ui/core, @object-ui/plugin-dashboard, @object-ui/app-shell exit 0. eslint over the six files: 0 errors.
  • Gates: dispatch-gates --commands derives no new family at this head. The 34 local re-runs exit 0.

Report table at 3e2eb1d0:

  • Ablation: the report's fmt reads measureField(measure)?.currency instead of measureCellCurrency(measureField(measure), row) (blob 8f61bf2d → 631d266c): 1 failed / 10 passed. Restored: blob == HEAD.
  • Tests: every plugin-report/src suite, 30 files, 278 passed, 7 skipped, exit 0 under os-verify-lock.
  • Type-check @object-ui/plugin-report exit 0. eslint: 0 errors. No new gate family. The 34 local re-runs exit 0.
Derived gates — 34 local runs, exit 0 each

Re-run locally at each head: the families dispatch-gates --commands matches by this card's paths, plus check:nul-bytes, check:cross-package-test-inputs, check:doc-authoring, check:issue-citations, check:type-check-coverage, check:keyed-text-bounds, check:role-word, check:adr-anchors, check:bash32-floor, check:cli-command-ids, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:pnpm-flag-spellings, check:scripts-symbol-anchors, and @objectstack/spec check:generated. The rest are CI's.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev


Generated by Claude Code

claude added 12 commits October 28, 2026 12:54
…unt points at a sibling ISO 4217 code field

Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
Co-authored-by: Claude <noreply@anthropic.com>
…ent in the dropped-refinements ledger

Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
Co-authored-by: Claude <noreply@anthropic.com>
…field naming convention, paid by tightening the currency block

Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
Co-authored-by: Claude <noreply@anthropic.com>
…cord-mode currencyField

Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
Co-authored-by: Claude <noreply@anthropic.com>
…d; cells, currency widget and create-form seeding follow it

Work in progress for the record currency display (ADR-0144 D2, D8 rule 4).

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
…er/group aggregates, dashboard/detail/gantt record reads

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
…e field-level answer never reads a code from a record-mode config

Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
Co-authored-by: Claude <noreply@anthropic.com>
…urrencyField row

The field form's new `currencyConfig.currencyField` row is one more
unwalked composite child (10 -> 11) and one more translated row label
in each locale's positive control (695 -> 696).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
… default

Under `currencyMode: 'record'` each record names its own code in the
sibling field, which only a per-row renderer can read. The field-level
answer is now `undefined` outright, so a record with a blank code shows
no symbol instead of a currency it never named.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
…A/A)

The code field's "Available" group now carries the codes the object's
records hold: the form renderer reads them once per object with one
grouped header query and hands them down on the field metadata
(`usedCurrencyCodes`). The ISO 4217 picker moves to
`@object-ui/components` as `CurrencyCodePicker`, shared with the filter.

A list filter row on a record-mode amount gains a required currency
picker defaulting to the organization currency; the builder emits the
code condition as a companion row, so the group lowers to
`amount op X AND code = C`. A paired group stays on AND.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored trailers below; also tightens the plugin-list test's literal type.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
The authorable-surface baseline is taken from main (#27233 retired
`CurrencyValue`) and rebuilt, keeping `currencyConfig.currencyField`;
the field reference page is regenerated from the merged tree.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
…-currency display branch

Bring-in order. Two conflicts:
- ui/packages/i18n/src/currency.ts: #27268 made record mode resolve to
  undefined with no tenant fallback; this branch reads the record's code
  when the caller passes the row. Kept the record read (a call with no
  record still resolves to undefined, which #27268's tests pin) and
  #27268's doc text.
- ObjectFieldInspector.tsx: both import blocks kept.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
…ency (#27188)

A grouped analytics answer over a `record`-mode amount marks the measure
column with `currencyField` (#27338), naming the result column that holds
each row's ISO 4217 code. `measureCellCurrency(field, row)` reads it
structurally (the spec type does not carry it yet) and returns that row's
code, nothing for a row without one, and the column's declared `currency`
for every other column. The dashboard table's body cells and the Studio
dataset preview read it off the RAW row, so a relabelled dimension cannot
move a code. Totals and cross-tab cells pass no row and draw no symbol.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
… its row's currency (#27188)

The grouped report table and its export snapshot now format each measure
cell through `measureCellCurrency`, reading the code off the RAW row
(`state.rows[i]`), as the dashboard table and the Studio preview do. The
grand total passes no row and draws no symbol on a record-mode column.
Every other column formats as before.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
…-irhbgx

# Conflicts:
#	ui/packages/i18n/src/currency.ts
#	ui/packages/plugin-form/src/SplitForm.tsx
…7188)

The merge of origin/main kept this branch's side of
`authorable-surface/data.json`, which still recorded the DataEngine RPC
request keys main retired (#27028). `gen:schema` on the merged tree drops
them; the result is byte-identical to main's file.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
)

`measureCellCurrency` had landed between `formatDimensionValue`'s JSDoc
and the function, orphaning the docblock. Move the helper (and its own
docblock) above it. No code change.

Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5

席位记录 · epic PM epic:#8345 · 2026-10-29T13:08Z

Head: 554bd578001abcad2468e4e1c77d3cd5b7b7031e · Session session_01Utp6CbTZrFqRYNgjSaSbN5

维护者速读

席位意见:可以合。席位亲自读过本卡 diff(e128ccc8 自审,以及其后的数据集按行币种 7d062908、报表表格 3e2eb1d0、docblock 归位 554bd578),读出的唯一问题(formatDimensionValue 的注释被插开)已在 554bd578 修好。os-zhuang 在草稿 c36aa49e 上的未请求批准不作为本次落地依据。

席位核对

ACCEPT · epic PM epic:#8345 · head 554bd578

ACCEPT → this comment.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 13:10
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 29, 2026 13:10
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 1ed3360 Oct 29, 2026
67 of 68 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-irhbgx branch October 29, 2026 13:34
os-bill pushed a commit that referenced this pull request Oct 29, 2026
…-ufaqzz

Brings in #27340 (ObjectFieldInspector, studio i18n) before the PR opens; no conflicts.

Claude-Session: https://claude.ai/code/session_01Utp6CbCrKUjGeTReNEmUJx
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/xl tests tooling

Projects

None yet

3 participants