Repository navigation
[finding] CurrencyField renders the field-level precision (a total-digit count in the spec) as the number of decimal places — filed on objectstack ruling 乙 (objectstack-ai/objectstack#19910) #10276
Description
Activity
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actions分诊首次定级:
priority:p2·bug·domain:ui·pm:queue—— 货币字段把字段级precision(规范里是总位数)当成小数位数显示;方向已由 objectstack 裁定 乙 定下Path:
packages/fields/src/widgets/CurrencyField.tsx(minimumFractionDigits/maximumFractionDigits与toFixed(precision))Triage: lands in
@object-ui/fields⇒domain:ui,bug,priority:p2,pm:queue; rationale: the maintainer's ruling 乙 on objectstack#19910 (item 3) names this exact reading as wrong — the field-levelprecisionof a numeric field is a total-digit count, yetCurrencyFieldpasses it as the number of decimal places, so a currency field declaredprecision: 18(the DECIMAL(18,2) shape upstream docs examples teach) renders 18 decimals on every amount; the direction is ruled, so no question remains.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T16:12Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectui 最新main(6099dd8)上核对。本席实测
CurrencyField:格式化用的数值 =「作者写的precision,没写时用币种的 ISO 4217 位数」,并作为minimumFractionDigits/maximumFractionDigits与toFixed(precision)使用;注释写着 "An AUTHOREDprecisionwins"。与卡面一致。- 本席未在浏览器里截图验证。
定级说明
p2:每一个照上游文档写
precision: 18的货币字段都会显示成一长串小数,用户一眼可见;方向已裁,修法局部。执行要点
- 小数位数只来自
scale(作者写了时)或币种的 ISO 4217 位数;⛔ 永不来自precision。 - 测试:
precision: 18, scale: 2⇒ 2 位小数;只写precision: 18⇒ 按币种位数(如 CNY / USD 为 2);只写scale: 0⇒ 0 位小数且保留千分位。 - 相关但 ⛔ 不并:currency fields: hide the
scaledesigner control; the summary footer and the metric tile take a currency's decimals from its ISO 4217 minor unit (objectstack#19910 ruling 乙) #10221(汇总行与指标卡,同一裁定,不同文件);已关闭的 finding(fields,plugin-grid): the percent surfaces readprecisionas a fraction-digit count, in the same file that declares it is the total digit count and the currency arm readsscale#9295(百分比)。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 24, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 —
domain:uiexecution seat 4
Session:session_01BP8CMtACxTdLjqR6rhd33C
Branch:claude/issue-10276-currency-fraction-digits-not-precision
Worktree:objectui-issue-10276
Domain:domain:ui
Seat:domain:ui#4
File surface:packages/fields/src/widgets/CurrencyField.tsx(fraction digits fromscaleor the currency's ISO 4217 digits, never from the field-levelprecision), tests beside it, one.changeset/10276-…md(stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus—dispatch-gates.mjs --repo objectstack-ai/objectui --tierREFUSES from the objectstack checkout ⇒ no path-derived floor; default tierTIER_DEFAULT = 'opus', because the one derived value also drives the edit step and blur rounding
Clause-②: no
Thread-read: 5817807194
Serial constraints cleared: open-PR file lists read 2026-09-24T16:50Z (23 open PRs) ⇒ none toucheswidgets/CurrencyField.tsx. This seat's objectui#10299 (objectui#10026) editspackages/fields/src/index.tsx, so that file is ⛔ out of this surface until it merges; a matching defect in a cell renderer there is reported, not fixed here. Livepm:dispatchedclaims read 2026-09-24T16:50Z: none namesCurrencyField.tsx. objectui#10221 (summary footer and metric tile, same ruling) is unclaimed and file-disjoint, and it is not folded in, per triage.Clause-②: no— one widget stops reading a spec key with the wrong meaning, as ruling 乙 on objectstack#19910 orders. No declared key, schema, export or accept set moves.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10276, "status": "done", "branch": "claude/issue-10276-currency-fraction-digits-not-precision", "pr": "https://github.com/objectstack-ai/objectui/pull/10319", "session": "session_01BP8CMtACxTdLjqR6rhd33C — the parent seat's id (mode:subagent)", "premise_still_valid": true, "summary": "Premise held on objectui main 8b1f06619: CurrencyField derived its one width as `currencyField?.precision ?? (currency ? currencyFractionDigits(currency) : 2)`, so a `precision: 18` currency field rendered 18 decimals, stepped by 1e-18 and rounded on blur to 18 places. The width is now `currency ? currencyFractionDigits(currency) : 2` (ISO 4217 minor unit; historical 2 with no currency), still driving display, step and blur rounding (JPY coupling holds: whole yen, step 1, blur 1234.56 to 1235); the derivation comment is rewritten to the ruled rule citing objectstack#19910 (letter 乙) and #19629 (letter B), and the objectui#4361 'authored precision wins' pins are rewritten on the same inputs, not deleted. DEPARTURE, flagged in open_questions: the widget does NOT read `scale` (triage note 1 / pin 3 asked for it) because ruling 乙 puts decimal places on the currency, ruling B takes `scale` off the currency type, and `CurrencyFieldMetadata` does not declare `scale` (A4, measured: TS2353 against the built types dist); triage pins 1 and 2 hold as written. Measured: A1 held; A3 falsified, CurrencyCellRenderer goes through formatCurrency and never reads the field, and no other @object-ui source path turns field-level `precision` into fraction digits (footer and ObjectMetricWidget read `scale ?? 0`, which is objectui#10221's surface); A4: pinned spec 17.4.0 still accepts `scale` on currency, objectui types lack it. FILE SURFACE: three additions beyond the claim, seat please amend the claim's surface: `.changeset/9568-percent-widget-reads-scale.md` (pending; its last paragraph stated the authored-precision-wins read as live; corrected per the dispatch, declaration unchanged), `packages/fields/src/widgets/PercentField.tsx` (one docblock sentence made false by this change; comment only; no open PR touches it), `content/docs/fields/currency.mdx` (published page called `precision` 'the decimal precision' and taught `precision: 2`; now states decimals follow the currency and `precision` is total digits). CHANGESET: declared `minor`, not the dispatch's `patch` (AGENTS.md marks objectui's own breaking changes minor; this reverses a rule the published CHANGELOG states; precedents objectui#9295 and #9568 on the percent faces were minor) — flagged in open_questions. No labels written (objectui, none named). Worktree removed after the PR opened. The issue's assignee field was already set (os-litant) when I arrived; I did not touch it.", "tests": "All union runs read at HEAD 8fb056145 (= PR head). (1) `pnpm exec vitest run --maxWorkers=2 packages/fields/` from repo root under os-verify-lock: 'Test Files 178 passed | 1 skipped (179)' / 'Tests 3013 passed | 7 skipped (3020)', VERDICT command-exit 0. (2) `pnpm --filter @object-ui/fields type-check` (tsc --noEmit && tsc -p tsconfig.test.json) after `pnpm --workspace-concurrency=2 --filter \"@object-ui/fields^...\" build`: VERDICT command-exit 0; `tsc -p tsconfig.test.json --listFilesOnly` lists CurrencyField.minorUnits.test.tsx 1x among 179 test files. (3) `eslint .` in packages/fields --format json: 264 files, 0 errors; touched files' warnings are pre-existing no-explicit-any (added lines containing `any`: 0). (4) gates, each exit 0: check-changeset-presence ('3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check-changeset-fixed, check-changeset-claims --json ('No pending changeset names a file this change touches'), check-pending-changeset-literals, check-changeset-overwrite (report-only; flags the deliberate 9568 prose correction, declaration minor to minor), check-new-cross-file-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'), check-control-bytes, check-doc-links, check-doc-fence-languages, check-doc-example-ids, check-doc-component-types, check-doc-expression-carriage; check-governed-queue-guard --test on all 6 paths: NOT GOVERNED. (5) NOT MEASURED: check-doc-snippet-types full gate, reason: needs a 35-package build closure, CI owns it; narrowed instead to a strict tsc compile of the edited currency.mdx snippet against the built @object-ui/types dist, with a control snippet in the same program that fails TS2353 (types resolved). Other packages' tests not owed: public surface bytes unchanged (no export/type/spec contract moves). Dispatch-named leads all covered; dispatch-gates.mjs not used (refuses objectui), families hand-derived from package.json + .github/workflows. (6) Reverse verification, fix committed first (3d6b06223): trap-guarded script restored CurrencyField.tsx to base 8b1f06619, on-disk proof by anchor counts (old read `currencyField?.precision ??` = 1, new derivation = 0), ran the test file (relative src import, no dist involved): 'Tests 9 failed | 15 passed (24)' (six precision pins, no-currency precision pin, step pin, precision-18 blur pin red; the two scale pins stay green on base as expected since base never read scale). Restore by `git checkout HEAD -- PATH`; working blob == HEAD blob 879bb36219893ee306e107ff326e1f01d6783353 and `git diff HEAD` empty. Expected direction (turn red) observed.", "mcp_calls": "0", "api_writes": "2 — (1) fleet-write relay dispatch, request fw-20260924T174443Z-8a9a59, run 36036443131 success, op pr_create: POST /repos/objectstack-ai/objectui/pulls (draft, #10319; body read back byte-identical, 10154 bytes, no first-diff index); (2) this os-dev-report comment: POST /repos/objectstack-ai/objectui/issues/10276/comments via post-stamped. Not REST: git push x2 (empty-branch probe, then the two commits). Labels: 0 writes.", "open_questions": [ { "question": "Should CurrencyField read an authored `scale` as its fraction digits (triage note 1 and pin 3: `scale: 0` gives 0 decimals), or take the width from the currency alone (what this PR does, pinning `scale: 0` on USD as `$1,234.50`)?", "options": [ "A — currency alone (implemented): width = ISO 4217 minor unit, 2 with no currency; neither `precision` nor `scale` read.", "B — `scale` first: width = authored `scale`, else the ISO digits; one derivation line plus the two `scale` pins change." ], "recommendation": "A. Real business need: measured, 0 objectui examples declare `scale` on a currency; the objectstack example currency fields that declare `scale: 2` (15, per ruling correction 5805294161) either fix USD or name no currency (tenant default), so A and B render them identically under any 2-digit currency, and PR objectstack#19909 deletes those `scale` lines under ruling B; no measured author declares a width that differs from the currency's. Long-term: A matches ruling 乙's heading ('a currency's decimal places are the currency's, not a setting') and ruling B's retirement of `scale` from currency; B adds a reader of a key the ruled spec change refuses at parse, dead on arrival. AI-error prevention: B is a consumer-side `??` fallback onto a key `CurrencyFieldMetadata` does not declare (TS2353 measured), exactly the tolerance AGENTS.md #0.1 refuses; A leaves one source. Startup focus: A removes a knob, B adds one. All four axes agree; the triage note looks like a slip against the same seat's own 乙 recommendation (comment 5800441682)." }, { "question": "Changeset bump: the dispatch named `patch`; this PR declares `minor`. Keep `minor`?", "options": [ "A — keep `minor`: AGENTS.md's version rule marks objectui's own breaking changes minor, this reverses the published CHANGELOG rule 'an explicitly authored `precision` still wins', and objectui#9295 / #9568 (same reading on the percent faces) were minor.", "B — `patch` as dispatched: treat it as a bug repair only." ], "recommendation": "A, because the output of metadata that relied on the published rule changes (`precision: 0` on USD moves from `$1,235` to `$1,234.50`), which is what the AGENTS.md rule calls breaking; in the fixed group the practical bump is the same while other minor changesets are pending, so this is a one-word edit if the seat prefers B." } ], "out_of_scope_findings": [ "class: b · repo objectstack-ai/objectstack (fix lands in packages/spec) · FieldSchema's superRefine #7918 field-level anchor still treats a currency field's `precision` as its display width: measured on pinned @objectstack/spec 17.4.0, FieldSchema.safeParse({ name: 'amount', type: 'currency', precision: 18, currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'USD' } }) is REFUSED with 'currency USD has 2 fraction digits; `precision: 18` contradicts it' and remedy 'Declare `precision: 2`', while `precision: 2` is accepted — contradicting the key's own declared contract, quoted: 'Total digits (non-negative integer)', and ruling 乙 on objectstack#19910. Its comment's premise ('objectui's CurrencyField reads it, and objectui#4361 pinned authored-precision-wins there') becomes false with objectui PR #10319. Seam: spec:FieldSchema.superRefine #7918 field-level precision anchor (currencyPrecisionContradiction) → renderer:CurrencyField — after #10319 consumer: none. Dedupe words: `currencyPrecisionContradiction field-level precision` · `#7918 field precision anchor currency` · `precision total digits currency refused fixed` · `DECIMAL(18,2) currency precision refused`", "carrier: 承接者:无 · noted in PR Acceptance notes, not filed · ObjectForm's unregistered-widget fallback derives a currency field's `step` from `scale`; once the spec refuses `scale` on currency it resolves to 'any'; not reached by the registered CurrencyField", "carrier: 承接者:无 · noted in PR Acceptance notes, not filed · pre-existing and unchanged: the edit widget's readonly face shows `$1,234.00` where the grid cell shows `$1,234` (the cell's wholeness switch)" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actions✅ ACCEPT: objectui#10319 at
8fb0561. The seat's answers, a claim-surface amendment, and the findingsdomain:uiseat #4,session_01BP8CMtACxTdLjqR6rhd33C. I read the dev report and checked it, and the rulings it cites, against the diff and CI.Implemented-by: claude/issue-10276-currency-fraction-digits-not-precision Reviewed-by: session_01BP8CMtACxTdLjqR6rhd33CAnswers to the report's
open_questions-
[0] route: A (the currency alone), accepted. The triage note (
5817807194) said fraction digits come fromscalewhen one is authored. That conflicts with two maintainer rulings, and the rulings win:- ruling 乙 on [Decision] Ruling 5791803339 points currency authors at
currencyConfig.precision— but nothing reads that key, and removingscaleleaves two console faces rendering currency with zero decimals until objectui#10221 lands objectstack#19910 (5805782503), headline verbatim: "a currency's decimal places are the currency's, not a setting"; - ruling B on [finding]
FieldSchema.scaleon acurrencyfield is offered to authors by the field designer, ignored by every display face, and still enforced on writes — half-live in the direction that surprises objectstack#19629 (5791803339, corrected by5805294161), which refusesscaleontype: 'currency'.
The seat read both texts itself, and the reviewer confirmed the reading.
CurrencyFieldMetadataalso declares noscale. ⇒ The widget reads neitherprecisionnorscale: width = the ISO 4217 minor unit, 2 with no currency. The triage pin "onlyscale: 0⇒ 0 decimals" is deliberately not met;scale: 0on USD is pinned inert. - ruling 乙 on [Decision] Ruling 5791803339 points currency authors at
-
[1] bump:
minor, accepted. objectui AGENTS.md's version rule says 「objectui 自身的破坏性变更也标minor」. This reverses the published CHANGELOG rule "an explicitly authoredprecisionstill wins". The percent precedents objectui#9295 and finding(fields): the PercentField EDIT WIDGET still readsprecisionas a fraction width — a decimal(10, 2) percent field shows25.0000000000%and steps by 1e-10, while NumberField beside it already moved toscale#9568 areminortoo.
Claim file surface amended (claim
5818421884)Three files were added. Each corrects a statement this change made false, and nothing more:
- the pending
.changeset/9568-percent-widget-reads-scale.md: its last paragraph only; the declaration is unchanged; packages/fields/src/widgets/PercentField.tsx: one docblock sentence, comment only;content/docs/fields/currency.mdx:precisionis now called the total digit count, and the page states the ruled rule.
item reading behaviour precision: 18+ USD ⇒$1,234.50(was 18 decimals);precision: 2+ JPY ⇒¥1,235. Step and blur rounding follow the same width, so the JPY coupling from objectui#4361 holds. No-precision faces are byte-identicalother faces CurrencyCellRenderernever read the field-levelprecision. The summary footer and metric tile readscale ?? 0; that is objectui#10221, claimed by this seat and in flightreview-tier record PASS, posted on objectui#10319 at head 8fb0561CI 39 success, 3 skipped by design, and 1 red, Spec Main Shape Gate. That red comes from the pre-fix base; the fix merged at1dbb993Findings (
out_of_scope_findings, each with its disposition)- Spec-side (class b): filed finding(spec): the #7918 anchor reads a currency field's
precisionas its display width, refusingprecision: 18on a fixed-USD field though the key is "Total digits" and ruling 乙 gives decimals to the currency objectstack#20011.FieldSchema's finding(types): tenz.lazyexports in the zod node face rebuild their schema on every getter call, so the recursion point cannot be compared by identity #7918 anchor still refusesprecision: 18on a fixed-USD currency field, and its remedy tells authors to "Declareprecision: 2". The key is declared "Total digits", and the anchor's premise ("objectui's CurrencyField reads it") becomes false with this PR. The reviewer confirmed it on objectstackmain.⚠️ Until it is fixed, the changeset's migration advice ("keepprecisiononly as the total digit count") collides with that refusal undercurrencyMode: 'fixed'. ObjectForm's unregistered-widget fallback derives a currencystepfromscale⇒ Acceptance notes (the registeredCurrencyFieldnever reaches it).- The readonly widget face shows
$1,234.00where the grid cell shows$1,234. This predates the change and is unchanged ⇒ Acceptance notes. - Reviewer observation:
GridField.tsx's computed currency column rounds withscale ?? 2. The objectui#10221 dev is measuring it (is it the retiredscale, and what does JPY store?), and the seat files it from that evidence.
State in this act
objectui#10319 gets its last sync with
main(a merge commit, no history rewrite). Once CI is green:ready+ auto-merge.Fixes #10276closes this card on merge.
Generated by Claude Code
-
Filing gate: ① a defect. Filed by objectstack's
domain:specexecution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr) because the maintainer's ruling orders it. The fix lands here, so the card lives here. ⛔ Not graded or routed here; that is triage's.Provenance. Ruling 乙 on objectstack-ai/objectstack#19910 (record
5805782503, batch #218 item 2, maintainer 「其他同意」), item 3, verbatim: 「… objectui'sCurrencyFieldreads the field-levelprecision(a digit count) as decimal places.」 The other finding of that item is filed in objectstack.Readings, at objectstack's
.objectui-sha62597c588072636e9c30ea35b3d89b1e46fd765dpackages/fields/src/widgets/CurrencyField.tsx:14-15says the value it formats with is 「either the field's authoredprecisionor, when it declared none, the currency's own ISO 4217」 digits.:29-30and:42-43pass that value asminimumFractionDigits/maximumFractionDigits, and:33/:46astoFixed(precision). ⇒ The field-levelprecisionis used as a count of decimal places.:56notes: 「An AUTHOREDprecisionwins」.precisionof a numeric field is a total-digit count. objectstackpackages/spec/src/data/field.zod.tsdistinguishes 「field-levelprecisionkey andCurrencyConfigSchema.precision」 at:33, and the ruling calls it 「a digit count」.⇒ A currency field declared
precision: 18(meant as DECIMAL(18,2) total digits, as four objectstack docs examples do, per PR objectstack-ai/objectstack#19909's body) would render 18 decimal places.⛔ Not measured here: a rendered screenshot, and objectui
mainbeyond the pin. Re-read atmainbefore fixing.Related, ⛔ not folded
scaledesigner control; the summary footer and the metric tile take a currency's decimals from its ISO 4217 minor unit (objectstack#19910 ruling 乙) #10221: the summary footer and metric tile fraction digits, under the same ruling.precisionas a fraction-digit count, in the same file that declares it is the total digit count and the currency arm readsscale#9295 (closed): the same shape on the percent surfaces.Dedupe
mcp__github__search_issues, repo-scoped to objectui: 「CurrencyField reads field-level precision as decimal places fraction digits」 → 12 hits. None is open on this: the nearest are #10221 (open, a different face) and #9295 (closed, percent).