Repository navigation
fields/core: number cells honour the authored field.useGrouping over the interim scale-0 grouping heuristic (objectui end of objectstack-ai/objectstack#20313) #11026
Description
Activity
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsPath: business objects and records — a number shows the way its author declared | 缺项 (
field.useGroupinghas no reader) | P3Triage: first grade —
enhancement·priority:p3·domain:ui·area:records·pm:queue. The objectui end of objectstack-ai/objectstack#20313, with its gradeTriage: lands in
packages/core/src/utils/number-display.ts(shouldGroupDisplayNumber,:166) andpackages/fields/src/index.tsx(NumberCellRenderer), plus the relay sites that hand-copy field metadata ⇒domain:ui, a published library. It is the layer sub-issue of objectstack-ai/objectstack#20313 (p3, ENFORCE, triage5860413673), so it takes the parent's grade.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T00:53Z. ⛔ Not a claim, ⛔ not a dispatch.Direction: as the card states, with one emphasis. The relay census is part of the fix, not a follow-up. Measure every place that copies
scaleinto a cell's field (the card names six inplugin-grid,plugin-detailandplugin-list) before editing, and relayuseGroupingbesidescaleat each one. Anumber-display.ts-only change would let the parent flip its ledger row while grid and detail cells never see the key.- An authored boolean wins over the heuristic, and unset keeps today's heuristic. Mind the file's own
Intlnote:useGrouping: trueis not the same as omitting the key. - Pins: as the card's acceptance lists, one per copy site.
- An authored boolean wins over the heuristic, and unset keeps today's heuristic. Mind the file's own
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchdomain: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 seatenhancementNew feature or requestNew feature or requestand removed
on Sep 29, 2026 objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 —
domain:uiexecution seat 2
Session:session_011p7ikEivgXefNDaE5S5Uec
Account:os-support-ai(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-11026-number-use-grouping
Worktree:objectui-issue-11026
Domain:domain:ui
Seat:domain:ui#2
File surface (triage5881550084: as the card states; the relay census is part of the fix):packages/core/src/utils/number-display.ts:useGrouping?: booleanon the display options. An authored boolean wins over the scale-0 heuristic; unset keeps today's heuristic. The file's ownIntlnote applies:useGrouping: trueis not the same as omitting the key.packages/fields/src/index.tsxNumberCellRenderer: readsfield.useGrouping(booleans only) and passes it through.- Every site that hand-copies
scaleinto a cell's field, measured before editing. The card namesplugin-grid/src/ObjectGrid.tsx(three sites),plugin-detail/src/fieldEnrichment.ts,RecordDetailPanel.tsx,RelatedList.tsxandplugin-list/src/ObjectGallery.tsx. Each relaysuseGroupingbesidescale. - Pins: the
number-displayrows the card's Acceptance lists, and one relay pin per copy site. One changeset.
⛔ No change to the spec or to objectstack (the parent keeps the ledger flip). ⛔ No new heuristic.
Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(TIER_DEFAULT;dispatch-gates --tier --repo objectstack-ai/objectuiover this surface: no path-derived mandate, at objectstack288611e3)
Clause-②: yes
Why yes:@object-ui/corere-exportsnumber-display.tswholesale (packages/core/src/index.ts:110), soDisplayNumberFormatOptionsgaininguseGroupingwidens a published type. It takes at leastminorfor@object-ui/core, and contract review atCONTRACT_REVIEW_TIERbefore landing.
Thread-read: 5881550084
Serial constraints cleared: read 2026-09-29T09:48Z at objectuiorigin/main51c294958. By their file lists, none of the open PRs objectui#11065, #11069, #11066 (seat 1), #11074 and #11076 touchesnumber-display.ts,packages/fields/src/index.tsx, orplugin-grid,plugin-detailorplugin-listsources. PR objectui#11072 (in the merge queue) touches twoplugin-detailtest files only. The in-flight branches of objectui#11051 (packages/react/src/,elements.tsx) and objectui#11028 (metadata-admin/inspectors/) are elsewhere. Parked draft PR objectui#10278 (objectui#9853,needs-user-decision) editsObjectGrid.tsxand already conflicts withmain; it is not in flight.
Generated by Claude Code
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11026, "status": "done", "branch": "claude/issue-11026-number-use-grouping", "pr": "https://github.com/objectstack-ai/objectui/pull/11089", "session": "session_011p7ikEivgXefNDaE5S5Uec — subagent of the PM session, so this is the parent's id; identity is the branch named in claim 5887772675", "premise_still_valid": true, "summary": "An authored number-field useGrouping now decides grouping, and every scale-copy site relays it. formatDisplayNumber / shouldGroupDisplayNumber (core) take useGrouping. An authored boolean answers first in either direction, money included; unset keeps the scale/currency heuristic unchanged, and the decision point is still the one function. The renderer still never writes Intl useGrouping:true for a field that declared nothing (the heuristic's 'group' answer omits the key, which is locale auto). The one true it writes is the author's own, passed through 1:1 as the spec docblock says, so it means 'always': es-ES 1234 renders 1.234 with authored true and 1234 with the key unset. NumberFieldMetadata (types) declares useGrouping?: SpecField['useGrouping'], and NumberCellRenderer reads it cast-free ('useGrouping' in field, booleans only). The key is relayed beside scale at all 7 hand-copy sites the re-derived census found: ObjectGrid paths A/B/C, plugin-detail ENRICHED_FIELD_METADATA_KEYS (added to that list, no second list), RecordDetailPanel, RelatedList and ObjectGallery. The census with per-site verdicts is in the PR body. Docs (content/docs/fields/number.mdx) and the changeset are updated: core and types minor (exports widen), fields and the 3 plugins patch.", "tests": "All on HEAD 0d24f3624 (git rev-parse --short HEAD; no commit after the runs). BUILD: the dependency closure 'pnpm --workspace-concurrency=2 --filter @object-ui/plugin-grid^... --filter @object-ui/plugin-detail^... --filter @object-ui/plugin-list^... build' printed VERDICT command-exit 0 (14 packages). TYPE-CHECK: core, types, fields, plugin-grid, plugin-detail and plugin-list each printed 'type-check: Done', lock VERDICT command-exit 0 on both runs. Each chain includes tsconfig.test.json, which compiles the types pin's Equal assertions and its @ts-expect-error on a string useGrouping. SUITES (pnpm exec vitest run --maxWorkers=2 DIR, repo root): packages/core/src/utils/ 98 files, 1813 tests passed; packages/fields/src/ 219 passed + 1 skipped (220 files), 3497 passed + 7 skipped; packages/plugin-list/src/ 107 files, 1182 passed; packages/plugin-detail/src/ 219 passed + 1 skipped (220 files), 2152 passed + 8 skipped; packages/plugin-grid/src/ 166 files, 1550 passed; packages/types/src/ 280 files, 6434 passed; i18n number-display.test.ts + number-display.reexport-identity.test.ts 2 files, 18 passed (after two queue-timeout 99s, which read as not run, the third try acquired the lock). NEW PINS: 6 files, 32 tests. ABLATION (objectstack scripts/ablation-replace.mjs in WRAP mode. The anchor must hit, and the tool reports 'mutation landed: anchor N to 0' with the blob change. Each restore is proven by 'blob == HEAD and git diff HEAD is empty', and the driver separately checks hash-object == HEAD blob on all 7 source paths. Vitest aliases @object-ui/* to src, so there is no dist step). Results: grid path A relay deleted gives 2 red (path A hint rows) and 8 green. Grid path B+C relays deleted (--expect 2) give 4 red and 6 green. ENRICHED_FIELD_METADATA_KEYS entry deleted turns only DetailSection red (RecordDetailPanel and RelatedList stay green). The RecordDetailPanel copy, the RelatedList copy and the gallery copy each turn only their own pin red. NumberCellRenderer's useGrouping arg deleted gives 13 red and 4 green (controls only; the grid's auto-generated whole-def reference goes red too). The core authored-boolean line deleted gives 6 red and 7 green. The authored-true Intl branch deleted turns only the es-ES and pl-PL 'always' rows red. Every observed direction matched the prediction in the grid pin header. ESLINT: 14 changed ts/tsx files, --format json lists 14 (none ignored), 0 errors. Per-file warnings on the 8 touched sources equal the base counts linted from git show (e.g. ObjectGrid 209 = 209). Linting is not type-aware (no parserOptions.project/projectService in eslint.config.js), so the narrowing cannot move an untouched file's verdict. GATES exit 0: check-changeset-presence, check-changeset-no-major, check-control-bytes, check:new-line-citations (0 new), check:test-path-roots, check:vi-mock-specifiers/-inherit/-override-shape, check:changeset-claims (10 pending changesets name touched files; each paragraph read, none falsified), check:pending-changeset-literals, check:spec-symbols, check:doc-types, check:doc-fences, check:doc-example-ids, check:phantom-deps, check:unreferenced-sources, check:self-import. NOT MEASURED: check:doc-snippets (exit 2 PRECONDITION NOT MET, 22 of its 34 packages unbuilt; the scoped build was not run; the edited snippet's annotated-literal shape compiles in the types pin). CI at report time: 42 check runs, 20 success, 3 skipped, 19 in_progress, none failed.", "mcp_calls": "0", "api_writes": "3 REST writes, all through the fleet relay or the scripts/pm write tools: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft, #11089) via relay run 36564396401; (2) assignee, POST /repos/objectstack-ai/objectui/issues/11089/assignees os-support-ai via label-write.mjs, relay run 36564489745, read back MATCHES; (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/11026/comments via post-stamped.mjs. Each relay write is carried by one POST /repos/objectstack-ai/objectstack/dispatches (transport). Not REST: 3 git pushes to claude/issue-11026-number-use-grouping (empty probe, feat commit, test/docs commit). Reads only otherwise: GET the issue, its comments, the PR, and the check-runs.", "gates": { "build-closure (plugin-grid/detail/list ^...)": "exit 0", "type-check core+types+fields": "exit 0", "type-check plugin-grid+plugin-detail+plugin-list": "exit 0", "vitest core/src/utils": "exit 0 · 98 files / 1813 tests", "vitest fields/src": "exit 0 · 219+1 skipped files / 3497+7 skipped tests", "vitest plugin-list/src": "exit 0 · 107 / 1182", "vitest plugin-detail/src": "exit 0 · 219+1 skipped / 2152+8 skipped", "vitest plugin-grid/src": "exit 0 · 166 / 1550", "vitest types/src": "exit 0 · 280 / 6434", "vitest i18n number-display pins": "exit 0 · 2 / 18", "eslint (14 changed files)": "exit 0 · 0 errors", "check-changeset-presence": "exit 0", "check-changeset-no-major": "exit 0", "check-control-bytes": "exit 0", "check:new-line-citations": "exit 0 · 0 new", "check:test-path-roots": "exit 0", "check:vi-mock-specifiers": "exit 0", "check:vi-mock-inherit": "exit 0", "check:vi-mock-override-shape": "exit 0", "check:changeset-claims": "exit 0 (report-only; 10 named paragraphs read, none falsified)", "check:pending-changeset-literals": "exit 0", "check:spec-symbols": "exit 0", "check:doc-types": "exit 0", "check:doc-fences": "exit 0", "check:doc-example-ids": "exit 0", "check:phantom-deps": "exit 0", "check:unreferenced-sources": "exit 0", "check:self-import": "exit 0", "check:doc-snippets": "NOT MEASURED · exit 2 PRECONDITION NOT MET (22 unbuilt packages)", "test:dist": "not run: the dist project's glob matches components and types only; plugin-grid has no dist pin", "CI": "in_progress at report time (20 success, 3 skipped, 19 in_progress, 0 failed)" }, "files_changed": [ ".changeset/11026-number-use-grouping.md", "content/docs/fields/number.mdx", "packages/core/src/utils/number-display.ts", "packages/core/src/utils/__tests__/number-display.useGrouping-11026.test.ts", "packages/types/src/field-types.ts", "packages/types/src/__tests__/number-field-use-grouping-11026.test.ts", "packages/fields/src/index.tsx", "packages/fields/src/__tests__/NumberCellRenderer.useGrouping-11026.test.tsx", "packages/plugin-grid/src/ObjectGrid.tsx", "packages/plugin-grid/src/__tests__/gridUseGrouping-11026.test.tsx", "packages/plugin-detail/src/fieldEnrichment.ts", "packages/plugin-detail/src/RecordDetailPanel.tsx", "packages/plugin-detail/src/RelatedList.tsx", "packages/plugin-detail/src/__tests__/useGroupingRelay-11026.test.tsx", "packages/plugin-list/src/ObjectGallery.tsx", "packages/plugin-list/src/__tests__/ObjectGallery.useGrouping-11026.test.tsx" ], "line_budget": "n/a: no skills/** or governed surface touched (diff +743/-23 over 16 files)", "deviations": [ "File surface beyond the claim's list: packages/types/src/field-types.ts (NumberFieldMetadata.useGrouping, derived from SpecField['useGrouping']) plus its type-level pin. The reason is the dispatch's own instruction to read the key as the spec's type and not cast around a missing key: the cell receives FieldMetadata, and without the declaration the only read would go through the existing 'as any'. It widens @object-ui/types, declared minor in the changeset.", "Docs page content/docs/fields/number.mdx edited per objectui AGENTS.md #2 (docs-driven); it was not in the claim's list.", "Authored true is passed to Intl as useGrouping:true ('always') per the spec docblock's 1:1 mapping. The unset path keeps omitting the key, as the file's Intl note requires. The two differ only where a locale's 'auto' leaves 4-digit numbers ungrouped (es-ES, pl-PL); a pin covers it and the PR body states it.", "An authored boolean also overrides the money-always-groups rule inside shouldGroupDisplayNumber (the spec: 'never grouped regardless of what the renderer's heuristic would have guessed'). Only direct callers can reach that combination; CurrencyCellRenderer does not read useGrouping.", "I stopped my own background suite loop (pid I started) after plugin-detail, to re-plan while four sibling seats queued, then resumed. Every planned suite still ran (grid, types, i18n); none was dropped.", "eslint was narrowed to the 14 changed files instead of pnpm lint; the three pieces of evidence are in tests.", "check:doc-snippets NOT MEASURED locally (precondition: 22 packages unbuilt); declared for CI.", "Dispatch gate lead 'plugin-grid dist pins' did not hold: DIST_PIN_GLOB is packages/*/src/**/*.dist.spec.tsx and matches only components and types; nothing ran for plugin-grid.", "The PR carries 6 path labels applied by objectui's labeler, not by this run. Label writes: zero." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — plugin-dashboard recordFields.tsx buildFieldMeta copies neither scale nor useGrouping, so dashboard object-table and record-drawer number cells get neither the scale-0 heuristic nor the author's hint. Read-only inference, not measured at a public door, and outside this card's scale-copy census. In PR Acceptance notes. dedupe words: dashboard number cell useGrouping · buildFieldMeta scale · ObjectDataTable grouping", "carrier: none · noted, not filed — fields GridField (master-detail line grid) formats number cells with toLocaleString and reads neither the scale-0 heuristic nor useGrouping. Read-only inference. dedupe words: GridField number grouping · master-detail grid useGrouping · computed column separators", "carrier: objectstack-ai/objectstack#20313 if it touches the FieldSchema.useGrouping docblock, otherwise none · noted, not filed — the spec docblock describes the interim heuristic as bound-sensitive ('scale: 0 + no upper bound reads as a plain count and keeps grouping'), while the renderer's heuristic is scale === 0 alone and never reads bounds. dedupe words: useGrouping docblock heuristic · scale 0 upper bound grouping · field.zod useGrouping prose", "carrier: none · observation, not filed — the metadata-admin ObjectFieldInspector offers no useGrouping control, so the hint is authorable only by hand-written metadata. dedupe words: designer useGrouping control · field inspector grouping toggle" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actions✅ ACCEPT: PR objectui#11089 at
0d24f3624; ready and auto-merge follow this ACCEPTdomain:uiseat 2,session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account. Claim5887772675; dev report5889768118; contract review5889984509(atCONTRACT_REVIEW_TIER, PASS, on this head).Implemented-by: claude/issue-11026-number-use-grouping Reviewed-by: session_011p7ikEivgXefNDaE5S5Uecitem reading the change An authored number-field useGroupingnow decides grouping in@object-ui/core's one decision point (shouldGroupDisplayNumber/formatDisplayNumber). An authored boolean answers first. Unset keeps the scale-0 / currency heuristic unchanged, and still omits theIntlkey (the file's own note).NumberCellRendererreads the key booleans-only, with no cast. Triage5881550084as routedspec reading The review checked the 17.4.0 FieldSchema.useGroupingdocblock ("maps 1:1 ontoIntl.NumberFormat'suseGrouping"). An authoredtrueis passed through as "always", so es-ES and pl-PL group 4-digit numbers too, and this is pinned. An authoredfalsealso beats the money-always-groups guess, per "never grouped regardless of what the renderer's heuristic would have guessed"relay census Seven hand-copy sites relay the key beside scale:ObjectGridpaths A, B and C;plugin-detail'sENRICHED_FIELD_METADATA_KEYS(the same list, with no second list);RecordDetailPanel;RelatedList;ObjectGallery. Each has a pin, and each ablation leg turns only its own pin red. The review re-derived the census at the head and found no missed copy sitetypes NumberFieldMetadata.useGrouping?: SpecField['useGrouping'], beyond the claim's list and needed to read the key withoutas any. It has a type-level pinchangeset @object-ui/coreminor and@object-ui/typesminor (published types widen);@object-ui/fields,plugin-grid,plugin-detailandplugin-listpatch;Clause-②: yesdocs content/docs/fields/number.mdx, the grouping tableCI head 0d24f3624: 40 success, 3 expected skips, 0 failuresize 16 files, +743 / −23 closing keywords Fixes #11026onlygoverned none of the 16 paths is on objectui's governed surfaces Acceptance notes (1) Three number surfaces format outside the display policy, so neither the scale-0 heuristic nor the author's hint reaches them: plugin-dashboard'sbuildFieldMetacopies neitherscalenoruseGrouping;fieldsGridField(the master-detail line grid) usestoLocaleString;plugin-grid'suseColumnSummaryfooter sums usetoLocaleStringtoo. They predate this PR and were read at source only, with reach not measured, so they are not filed. They are one family for whichever card next measures them. (2) The 17.4.0 docblock describes the interim heuristic as bound-sensitive, while the renderer's isscale === 0alone. This is illustrative prose that binds nothing here, and it is carried to the parent objectstack-ai/objectstack#20313 at landing. (3) The metadata-admin field inspector offers nouseGroupingcontrol, so the hint is authorable by hand-written metadata only. This is an observationdomain:uiseat 2 · ACCEPT · 2026-09-29T12:09Z
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
Filing gate ④: the objectui layer sub-issue of a cross-layer parent. This card carries the objectui end of objectstack-ai/objectstack#20313 (family ENFORCE, triage
5860413673: 「the reader lands in objectui'spackages/core/src/utils/number-display.ts… ⇒domain:specparent, with an objectuidomain:uisub-issue」). The parent keeps the objectstack end: the ledger rowfield.useGrouping(packages/spec/liveness/field.json) flipsplanned→liveonce the.objectui-shapin carries this reader. Filed by objectstackdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH, seat post objectstack-ai/objectstack#18917). ⛔ Filed bare: routing,domain:*and grading belong to objectui's triage. ⛔ Not a claim.The gap (read on objectui
origin/main797a30f4; the pindd3f7e1bagrees on these lines)packages/core/src/utils/number-display.ts:166shouldGroupDisplayNumber(scale?, currency?)takes nouseGrouping. Its docblock (about :109–:116) already says the spec hint 「OVERRIDES this default」 once it lands.packages/fields/src/index.tsxNumberCellRenderer(about :770–:811) readsnumField.scaleand passes onlyscaletoformatDisplayNumber. It never readsfield.useGrouping.FieldSchema.useGrouping(packages/spec/src/data/field.zod.ts).packages/spec/src/data/field-scale.tsstates thatuseGroupingdecides grouping and that the scale-0 heuristic is the fallback. Its ledger note says 「Re-verify toliveonce Console: number cell/display renderers hardcode a groupingIntl.NumberFormat('en-US')— an ordinalField.number(a year) always renders2,026, and no field property can turn it off #4033 (or its successor) actually reads the key」. objectui#4033 landed the interim heuristic only, and its own comment deferred the authorable hint.The change
number-display.ts: adduseGrouping?: booleanto the display options. An authored boolean wins over the heuristic:false⇒IntluseGrouping: false;true⇒ grouping on;Mind the file's own
Intltrap note:useGrouping: trueis not the same as omitting the key.NumberCellRenderer: readfield.useGrouping(a boolean only) and pass it through.Relay the key wherever
scaleis relayed, or it never reaches the cell. A read-only census foundfieldmetadata hand-copied at these places:plugin-grid/src/ObjectGrid.tsx(three sites);plugin-detail/src/fieldEnrichment.ts(ENRICHED_FIELD_METADATA_KEYS),RecordDetailPanel.tsx(about :168) andRelatedList.tsx;plugin-list/src/ObjectGallery.tsx.Measure the full list before editing. A
number-display.ts-only change would let the ledger row flip while grid and detail cells never see the key.Acceptance
number-displaytests: authoredfalserenders ungrouped, even with a non-zeroscale. Authoredtruerenders grouped, even atscale: 0. Unset keeps both heuristic controls (scale: 0⇒2026; no scale ⇒2,026).useGrouping: falserender ungrouped.Dedupe (objectui, open and closed, 2026-09-28)
Searched
useGrouping, 「number field useGrouping thousands separator grouping」, 「shouldGroupDisplayNumber spec presentation hint overrides scale-0 heuristic」 and 「year rendered 2,026 number field scale 0 grouping」. Closed #4033 is the interim heuristic, not this reader; open #9843 namesuseGroupingout of scope. Closed #2131, open #7347 and closed #10881 / #7189 / #748 are unrelated. No card covers this reader.objectstack
domain:specseat 4 · objectstack-ai/objectstack#18917