feat(spec): Field.number gains useGrouping presentation hint (#7768) - #7813
feat(spec): Field.number gains useGrouping presentation hint (#7768)#7813os-zhuang wants to merge 2 commits into
Conversation
FieldSchema gains an optional `useGrouping: boolean` (Option A, ruled 2026-08-11 on #7768, maintainer veto window open) so an authored number field can opt out of Intl.NumberFormat's digit grouping without losing numeric semantics -- the fix for years (Field.number({ scale: 0, min: 1900 })) rendering as "2,026" that downstream apps have worked around three times by converting to Field.text (hotcrm-heimao#35/#40/#59). No default is declared: absent defers to the renderer (interim heuristic today, locale default eventually -- objectui#4033's contract, not this package's). Threads through Field.number(...) automatically via the existing FieldInput shape, same as scale/min. Also: liveness ledger classifies the key `planned` (objectui#4033 is the pending consumer); authorable-surface/data.json, field.mdx and state-counts.md regenerated to match.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7768
Problem
Field.number's only presentation-adjacent property isscale, which governsdecimal places, not digit grouping. Console number renderers construct
Intl.NumberFormatwith grouping unconditionally ON, so an ordinal/identifierinteger authored as
Field.number({ scale: 0, min: 1900 })(a year) renders2,026everywhere it is shown. Downstream apps hit this three times(hotcrm-heimao#35, #40, #59), each time converting the field to
Field.textto escape the comma — trading away numeric semantics (range validation,
sort-as-number, arithmetic) for a display detail unrelated to the field's type.
Ruled direction (provenance)
Option A ruled on #7768, 2026-08-11 (PM triage comment), maintainer veto
window noted and still open: add a narrow
useGrouping?: booleanmapping 1:1to
Intl.NumberFormat'suseGrouping. Option B (displayFormat, a broaderpresentation slot) explicitly rejected as capability surface without demand
per the maintainer's #7496 norm — narrowest shape with measured pull wins.
What changed
packages/spec/src/data/field.zod.ts—FieldSchemagainsuseGrouping: z.boolean().optional(), flat alongsideprecision/scale/min/max("Number Constraints"). Docblock explains the three-valued contract:
heuristic today (objectui#4033), the locale's own default eventually.
That contract lives in objectui, not here.
false— explicit author opt-out (a year / ID / zip code).true— author pins grouping on.Threads automatically through
Field.number(...)(and every otherbuilder) via the existing
FieldInputshape — no builder special-casingneeded, the same way
scale/mintravel today. Unknown-key strictness onFieldSchema(strictObject, 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001) is unaffected — verified with adedicated test.
Tests added to
packages/spec/src/data/field.test.ts: accepts explicittrue/false, absent stays absent (no materialized default), rejectsnon-boolean, confirms
Field.number(...)threading, confirms unknown-keystrictness is unchanged, and pins the JSON-Schema projection (
booleantype, no
default, not inrequired) the wayfield-autonumber-default-format.test.tspinsautonumberFormat's.Regenerated:
authorable-surface/+authorable-surface.base.json+json-schema/(viapnpm gen:schema) andcontent/docs/references/data/field.mdx(via the docs gate), which now list
useGroupingin thedata/Field:keyset / reference table.
packages/spec/liveness/field.json— classified the new keyplanned(not
live): it has no runtime consumer yet, objectui#4033 is theconsumer landing next. Not
authorWarn'd, per the ledger's own rule —unlike a speculative future phase, an author who sets this today loses
nothing and needs no re-authoring once the objectui half lands.
packages/spec/liveness/state-counts.mdregenerated to match(
pnpm gen:liveness-counts);check:livenessis green.Scope note — sibling authoring surfaces deliberately NOT touched
scale/min/precisionalso travel into two Setup-app admin field-editorform definitions (
packages/spec/src/data/field.form.ts,packages/spec/src/data/object.form.ts) and intoui/view.zod.ts'sFormFieldBaseSchema(per-view field override for end-user Form views). Ichecked all three; none were extended. Exposing
useGroupingas a Studio UItoggle (which would also need
pnpm i18n:extractacross 4 locale bundles) oras a per-view override is a UX decision the ruling did not make, and Option A
was explicitly scoped to "the number field's authorable schema" — extending
those surfaces now would be widening past the ruled direction while the
maintainer veto window is still open. Flagging as a candidate follow-up.
Renderer contract (not implemented here)
explicit hint > interim heuristic > locale default. objectui#4033 is the
consumer that reads this key and retires the interim heuristic
(
scale === 0 && no currency⇒ ungrouped).Gate results
Local, scoped to this surface (full commands/output in the dev report
comment on #7768):
pnpm --filter @objectstack/spec test(full package, 378 files): 376passed, 1 pre-existing failure (
api-methods-batch-conformance.test.tsonsys_api_key, introduced by fix(platform-objects,plugin-auth): give API-key revoke/restore a working product route #7769, tracked as Queue-blocking: #7769 openedupdateon sys_api_key withoutbulk—api-methods-batch-conformancefails every merge-queue (full-suite) build #7793 — not touched by thisPR), 1 file transiently red mid-run on my own uncommitted staleness
(
build-schemas-check-mode.test.ts, re-run clean aftergen:schema: 60/60).pnpm --filter @objectstack/spec typecheck: clean, zero errors.pnpm --filter @objectstack/spec build: clean.pnpm --filter @objectstack/spec check:generated: all 13 generatedartifacts current.
check:adr-anchors,check:changeset-gate-self-tests(+ realempty-changeset/adr-0087-registration runs against the committed diff),
check:docs-audit-scope,check:driver-conformance,check:merge-driver,check:release-body,check:spec-parsed-alias,check:nul-bytes: allgreen.
check:i18n— skipped: needs the built@objectstack/cli(prerequisitethis worktree doesn't carry), and this PR touches no i18n-extraction
surface (no
field.form.ts/object.form.ts/translation file changed) sothere is nothing for it to see.
--filter @objectstack/lint check:doc-formula-expressions— skipped: needsthe built
@objectstack/formulapackage, unrelated to this schema-onlychange (no formula-expression doc content touched).
Landing
Per the claim comment, this PR is expected to touch
content/docs/references/**and joins the os-regen one-at-a-time landing relay
(#7758 → #7759 → #7763 → #7782). Left as draft — the PM lands it.
Generated by Claude Code