Skip to content

fix(plugin-detail): record page body tables total only declared-additive columns, not ordinals - #29966

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-bo68rr
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-bo68rr

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #29943

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:记录页正文子表(全宽明细表)底部的「本页合计」不再把所有数字列相加:金额(currency)列在本页各行同一币种时合计并按该币种显示;普通数字列只有在父对象声明了对该列 sum 的汇总字段时才合计;其余列不合计,没有可合计的列时整行不画。
  • 为什么改:hotclm 合同页把版本号、期次也加了起来(「本页合计 版本号 10」「期次 15」),EUR 合同的金额还按无币种裸数相加;字段类型本身分不清「序号」和「数量」,只能读作者已经声明过的东西。
  • 风险与回滚:已发布页面的可见行为变化:以前被合计的普通数字列(数量等)若父对象没有 sum 汇总,合计会消失;币种混合或未知的金额列也不再合计。只改 BodyTableTotals 一处,不发新请求、不加 spec 键;回滚即还原本 PR。
  • 先看:ui/packages/plugin-detail/src/RelatedList.tsx 的 BodyTableTotals 与新增的 rolledUpSumFields / agreedRowCurrency;ui/packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx 新增的 objectstack#29943 用例组。
  • 两分钟验证:在仓根运行 pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx,预期 12 个测试全部通过(序号列无合计、金额列按 € 合计、父对象 sum 汇总的数字列才合计、混合币种无合计)。
  • 需要你做的:无,席位落地。

What changes

BodyTableTotals (the totals line under a body table of the default record page, objectstack#27055 Target 8) totalled every column typed currency | number | integer | decimal. Target 8 promised "a totals row for numeric columns", and the type alone cannot tell an ordinal from a quantity: clm_contract_version.version_no and clm_payment_plan.seq are Field.number with scale: 0. The line now totals only what something already declared additive:

Column Before After
currency, every counted row in one known currency summed, code from currencyConfig.defaultCurrency ?? currency whatever the mode summed, formatted in that currency through formatCurrency (the cell's formatter)
currency, rows in different currencies, or one row's currency unknown summed (bare, or in the field's defaultCurrency) no total
number / integer / decimal, the parent object rolls it up with sum summed summed (unchanged)
number / integer / decimal, no such roll-up (ordinals, and amounts stored as number) summed no total
anything else no total no total

When no column qualifies the line is not drawn, as before.

Which currency a row is in. Each counted row resolves through @object-ui/i18n's resolveFieldCurrency(def, tenantCurrency, row), the reader the currency cell and the grid footer (useColumnSummary) already share: the field's code under currencyMode: 'fixed', the tenant default (useLocalization().currency) under dynamic, the row's sibling code field (currencyConfig.currencyField) under record. One consequence for a fixture that carried currencyConfig: { defaultCurrency: 'USD' } with no mode: the spec parses that to dynamic and does not read defaultCurrency there, so the total now shows the tenant default, the same currency the column's cells display. The old reader ignored the mode and could print a total in a currency the cells above it did not show. The existing #27055 fixture now declares currencyMode: 'fixed'.

Which number columns a roll-up declares additive (the reader used). rolledUpSumFields reads the record page's own object schema from useRecordContext().objectSchema, which RecordDetailView provides (objectSchema={objectDef}) around RecordPageColumns, the only place a body table is drawn. A parent field counts when it is type: 'summary' with summaryOperations.function === 'sum', summaryOperations.object naming this list's object (api || objectName), and summaryOperations.field naming the column. Both field-container shapes the metadata API serves are read. relationshipField is not compared, because which foreign key a sum runs through does not change whether the child column adds up. No fetch was added: the parent schema was already in context (the PM's mechanism assumption holds).

Tests

New top-level describe in the existing body-table harness, RelatedList.bodyTable-27055.test.tsx (hotclm's payment-plan fixture):

Ablation. The fix commit restored to the old type set (the gate line if (!isCurrency && !(ROLLED_UP_FIELD_TYPES.has(def.type) && rolledUp.has(key))) continue; replaced by if (!isCurrency && !ROLLED_UP_FIELD_TYPES.has(def.type)) continue;, i.e. every number column totalled) through scripts/ablation-replace.mjs: anchor 1 → 0, blob 4270c0b477d1 → d31d47e2ca1d; 5 tests red, including "totals no ordinal column — and draws no line when nothing else adds up" and the #27055 case. Restored: blob == HEAD 4270c0b477d1, git diff HEAD empty. The test imports ../RelatedList from source, so no build sat between the mutation and the run.

Acceptance notes

  • hotclm's amounts (what the order asked me to report). Per the card, hotclm's payment-plan planned_amount / actual_amount are number fields whose currency lives on the parent contract's currency_code. Under this rule they get no total unless the contract declares a summary field that sums them; with such a roll-up they total as plain numbers, with no currency symbol, because a number column carries no currency of its own. The way to get a currency-formatted total is the spec's own: declare the amount as currency with currencyMode: 'record' and a currencyField on the child row. The parent's currency_code is not read, because ADR-0144 D2 puts a record-mode amount's code on the amount's own object. I could not open the hotclm screenshots or metadata from this container (the path in the order is not in this project's shared folder), so this reading comes from the card text.
  • A record-mode amount whose code column is not drawn. An authored-column related list projects ($select) only the drawn columns plus predicate operands, so a record-mode amount whose currencyField is not among the columns arrives without its code. The total then reads as unknown and is not drawn (honest, not wrong). The cell above it has the same gap. Adding the code field to the projection is a fetch change outside this card. Reported in the report, not filed.
  • No new spec key; Metadata-protocol: none.
  • In-flight overlap re-checked at PR time: Studio All-metadata item page: on a locked item the Properties panel shown with nothing selected disables every control with no reason on hover, keyboard focus or screen reader #29897's PR fix(app-shell): on a read-only item, the Properties panel shown with nothing selected says why its controls are disabled #29958 touches only app-shell/.../ResourceEditPage.tsx; no open PR touches ui/packages/plugin-detail/** beside this one.

Verification

All on head ad05c644 (shared box; lock VERDICT lines quoted).

Build, type-check, vitest
  • pnpm --workspace-concurrency=2 --filter "@object-ui/plugin-detail^..." build: VERDICT command-exit 0 (6m04s)
  • pnpm --filter @object-ui/plugin-detail type-check (tsc --noEmit && tsc -p tsconfig.test.json): VERDICT command-exit 0
  • pnpm --filter @object-ui/plugin-detail build, then pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-detail/: VERDICT command-exit 0 · Test Files 369 passed | 1 skipped (370) · Tests 3250 passed | 18 skipped (3268)
  • pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx: 12 passed (12)
Derived gates (`dispatch-gates --commands`, 35 lines, then `--ran`)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

node scripts/pm/dispatch-gates.mjs --ran FILE --repo objectstack-ai/objectstack: exit 0, "Run reconciliation — 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN."

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran. pnpm lint was not run locally: the repo-wide lint is the CI run.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 section is the dispatch's required Chinese summary).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzfSkDtGdeCZgsqhyHH

…olumns

A record page body table totalled every currency/number/integer/decimal
column, so ordinals such as a version number or an instalment number were
summed. A currency column now totals only when its counted rows share one
known currency (the cell's own resolveFieldCurrency), and a number column
only when the record's own object rolls it up with sum.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfSkDtGdeCZgsqhyHH
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzjDyeFwq6AuFu3vwTd

维护者速读(席位终稿)

  • 改了什么:记录页正文子表底部的「本页合计」不再把所有数字列相加。金额列只在本页各行同一币种时合计,并按该币种显示;普通数字列只有父对象声明了对该列 sum 的汇总字段才合计;没有可合计的列时整行不画。
  • 为什么改:hotclm 合同页把版本号、期次也加了起来(「版本号 10」「期次 15」),EUR 金额还按无币种裸数相加。字段类型分不清序号和数量,只能读作者已声明的汇总。
  • 风险与回滚:已发布页面的可见变化:没有父对象 sum 汇总的数量列不再显示合计,混合或未知币种的金额列也不再合计。只改 BodyTableTotals 一处,不发请求、不加 spec 键;回滚即还原本 PR。
  • 先看:RelatedList.tsx 的 BodyTableTotals、rolledUpSumFields、agreedRowCurrency。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx,12 个用例全过。
  • 需要你做的:无,非受管面,CI 全绿后席位落地。
  • 席位意见:ACCEPT。规则与派发令一致(金额同币种 + 父对象 sum 汇总的数字列),币种读取复用单元格同一个 resolveFieldCurrency,合计与上方单元格不会再显示不同币种。

席位核对 · ACCEPT · head ad05c644c36cb0ebda2934afadd5d069648fed89 · 2026-11-03T18:24Z

  1. Shape: draft, base main, attribution block then Fixes #29943 (whole card lands). One changeset (@object-ui/plugin-detail patch) with Clause-②: no / Metadata-protocol: none and the visible-behaviour paragraph; no content/docs/releases/; 3 files, +260/−27. check-governed-merges --pr 29966: NOT governed, 287 lines ≤ 3000. No contract face; this seat's read is the review of record.
  2. Premise, measured: the readers the diff leans on exist on origin/main: resolveFieldCurrency (ui/packages/i18n/src/currency.ts:97; record mode reads the row's code field, otherwise currency → fixed default → defaultCurrency → tenant), formatCurrency (ui/packages/fields/src/widgets/number-format.ts:159), useRecordContext (ui/packages/react/src/context/RecordContext.tsx:253), and RecordDetailView wraps the page in RecordContextProvider objectSchema={objectDef} (~L4902), the only place a body table is drawn. The ordinal premise matches the card: version_no / seq are Field.number, indistinguishable by type. Branch is 3 behind main at merge base 40fa4335; the 3 commits touch no ui/ file.
  3. Gates: dev's local union at ad05c644: the test file 12/12, plugin-detail suite green, type-check green, dispatch-gates --commands 35/35 exit 0; ablation turns the 5 new rule cases red, CONTROL cases stay green. The existing console record page v3: frameless detail sections, one merged timeline (discussion + activity + field changes), compact related cards, header 1 primary + 2 secondary + ⋯ — the synthesized default page, drawn from the object's roles; the dead BuildPageOptions hints go #27055 fixture now declares currencyMode: 'fixed', which is the spec-correct way to get a fixed code and is named in the changeset. CI on this head was in progress at this reading; landing waits for every check green or expected-skip.
  4. Accepted deviation: a currencyConfig.defaultCurrency without currencyMode: 'fixed' no longer drives the total's currency; the total follows the cells (tenant default under dynamic). This aligns the footer with the cell reader, not a new rule. Accepted. The roll-up's relationshipField is not compared: whether a child column is additive does not depend on which foreign key sums it. Accepted.
  5. Out of scope: no spec key for "this column is additive" (card's option B, not ordered); the grid footer (useColumnSummary) unchanged. Skills-docs: not owed. mcp_calls: reads only, no write tool.
  6. Landing: ordinary queue. When every check on this head is green or expected-skip: relay pr_ready + automerge_enable, follow to MERGED, record the landing on Record page v3 body tables total every number column — a version number and an instalment number are summed into 「本页合计 版本号 10 / 期次 15」 #29943.

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 size/m tooling

Projects

None yet

1 participant