This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit c1763e5
fix(plugin-gantt,fields): the gantt numeric tooltip row and NumberCellRenderer read the field width through resolveFieldScale (objectui#11254) (#11412)
Fixes #11254
Clause-②: no
The two same-family carriers the director's ruling (card comment
5933283974) left on this card now read the field width through
`resolveFieldScale` from `@objectstack/spec/data` (ruling A′,
objectstack-ai/objectstack#19628): the gantt tooltip's numeric row and
`NumberCellRenderer`. Out, as ruled: `formatPercent`'s `precision = 0`
default, `progress`, `GroupRow`'s `toFixed(2)`, and the tile's `sum` /
`avg` over an undeclared `number` (ruling A, deferred to its trigger).
None of them is touched.
## What changes
- **`packages/plugin-gantt/src/ObjectGantt.tsx`**, `formatFieldValue`,
the `number` / `integer` / `float` / `decimal` case. It called
`formatNumber(Number(value), undefined, displayLocale)`. The `undefined`
took `formatNumber`'s two-place parameter default, so every numeric row
read two decimals whatever the field declared. The row now resolves
`resolveFieldScale({ type: def?.type, scale: def?.scale })` and renders
through `formatDisplayNumber` (from `@object-ui/i18n`, already a
dependency) in the two arms `NumberCellRenderer` uses. A resolved width
fixes both bounds. `undefined` means no fixed width: minimum 0, maximum
20, the value's natural precision. `formatNumber` is no longer imported
here. **Width only:** no `scale` reaches the grouping policy on this
row, so it groups exactly as before (see Acceptance notes).
- **`packages/fields/src/index.tsx`**, `NumberCellRenderer`. `typeof
numField.scale === 'number' ? numField.scale : undefined` becomes
`resolveFieldScale({ type: numField.type, scale: numField.scale })`. The
`minimumFractionDigits: scale ?? 0` / `maximumFractionDigits: scale ??
20` pair becomes the same explicit two-arm spread, so no `?? N` spelling
sits beside the resolver. A well-formed or absent declaration renders
byte-identically. The resolved value feeds `formatDisplayNumber`'s
`scale` (the grouping-policy input) exactly as the raw one did.
- **Two existing gantt pins rode on the retired two-place constant**
(fixture triage, one disposition each):
- `ObjectGantt.test.tsx`, `resolves and formats tooltipFields per
record`: `effort` declares no `scale`, so its expectation moves from
`Effort=12.00` to `Effort=12`. A comment beside it names the ruling.
- `ObjectGantt.numberLocale.test.tsx`: that file measures the locale,
not the width. Its `qty` fixture now **declares** `scale: 2`, so every
locale expectation (`1.234,50`, `1,234.50`) keeps its bytes through the
declared path. The width itself is pinned in the new file.
- Pins:
`packages/plugin-gantt/src/ObjectGantt.numberResolvedWidth-11254.test.tsx`
(6) and
`packages/fields/src/__tests__/NumberCellRenderer.resolvedWidth-11254.test.tsx`
(6).
- Changesets (both `patch`):
`.changeset/11254-gantt-number-resolved-width.md`
(`@object-ui/plugin-gantt`) and
`.changeset/11254-number-cell-resolved-width.md` (`@object-ui/fields`).
No manifest moves. Both packages already declare `@objectstack/spec`
`^17.5.0` and already imported `resolveFieldScale` on `main`.
## Premises measured first (the dispatch's Zone 2)
1. **Gantt row: confirmed.** At base `7728c67c86` the case returns
`formatNumber(Number(value), undefined, displayLocale)`.
`formatNumber(value, decimals = 2, locale)` sets `minimumFractionDigits`
and `maximumFractionDigits` both to `decimals`, so `undefined` pads to
two places. It **cannot** express "no fixed width": every input fixes
both bounds to one number. The existing helper that can is
`formatDisplayNumber`, which `formatNumber` and `NumberCellRenderer`
both call. No new helper and no new export.
2. **The resolver, read in the installed `@objectstack/spec` 17.5.0**
(`dist/data/index.mjs`, `resolveFieldScale` and `ABSENT_SCALE_BY_TYPE`).
A declared `scale` is returned when `Number.isInteger(scale)` and it is
at least 0. Otherwise the type's row applies, and the table has **one
row, `percent: 0`**. So `number`, `integer`, `float` and `decimal`
answer the declared width when well-formed and `undefined` otherwise. A
malformed `scale: "2"`, `1.5` or `-1` answers `undefined`. There is no
clamp, so `101` comes back verbatim; `FieldSchema` refuses it at
authoring. **The `integer` answer is `undefined`**, so the gantt
`integer` row changes too: a stored `7` read `7.00` and now reads `7`.
3. **`NumberCellRenderer`: confirmed, with a correction to the
example.** It differs from the resolver only for a malformed declaration
that is still a JS number. The string `"2"` is **not** such a case:
`typeof "2"` is not `'number'`, so both reads answer `undefined`, and it
is pinned as a control. The cases that differ are:
- `scale: 1.5`: `Intl` floors it, so `3.14159` read `3.1`;
- `scale: -1`: `Intl` throws `RangeError: minimumFractionDigits value is
out of range.` and takes the row down. `NaN` behaves the same way.
Grouping: every malformed value is not `0`, so the old read grouped, and
the resolver's `undefined` groups too. A declared `0` stays `0` and
renders ungrouped. Both halves are pinned as controls.
## Pins: red first, then green
- **Red at `cee8642`** (pins only, base source): `pnpm exec vitest run
--maxWorkers=2` over the two new files gave `Tests 7 failed | 5 passed
(12)`, VERDICT command-exit 1, and every direction matched the one
predicted in the file headers:
- gantt `scale: 3`: `Expected "1.500" Received "1.50"`;
- gantt `scale: 0`: `Expected "2" Received "1.50"`;
- gantt undeclared: `Expected "1.5" Received "1.50"`;
- gantt `integer`: `Expected "7" Received "7.00"`;
- gantt malformed `"2"`: `Expected "1.5" Received "1.50"`;
- cell `scale: 1.5`: `Expected "3.14159" Received "3.1"`;
- cell `scale: -1`: `RangeError: minimumFractionDigits value is out of
range.`
The green ones were the gantt `scale: 2` row (the old default equals the
declaration) and the cell controls (`"2"`, `2`, absent, and the `scale:
0` / absent grouping pair).
- **Green at `b4f818f`**: the two new files plus `ObjectGantt.test.tsx`,
`ObjectGantt.numberLocale.test.tsx` and
`ObjectGantt.percentResolvedWidth-11254.test.tsx` gave `Test Files 5
passed (5)`, `Tests 55 passed (55)`, VERDICT command-exit 0.
- The gantt rows also assert agreement with the **real**
`NumberCellRenderer` for the same value and field, beside absolute
bytes. The vitest config aliases `@object-ui/*` to `src`, and neither
package had a `dist` at the time, so both legs measured source. The red
leg is the before-state of the ablation: the same pins, with no line of
the fix present.
## Gates, all at head `b4f818f`
- Build closure: `pnpm --workspace-concurrency=2 --filter
'@object-ui/plugin-gantt^...' --filter '@object-ui/fields^...' build`
built `Scope: 13 of 47 workspace projects`, VERDICT command-exit 0. Then
`--filter @object-ui/plugin-gantt --filter @object-ui/fields build`,
VERDICT command-exit 0.
- `type-check`: `--filter @object-ui/plugin-gantt --filter
@object-ui/fields type-check` echoed `packages/fields type-check: Done`
and `packages/plugin-gantt type-check: Done`, VERDICT command-exit 0.
`tsc -p tsconfig.test.json --listFilesOnly` lists both new pins and both
moved files.
- Suites: `pnpm exec vitest run --maxWorkers=2 packages/plugin-gantt/
packages/fields/` gave `Test Files 325 passed | 2 skipped (327)` and
`Tests 4291 passed | 121 skipped (4412)`, VERDICT command-exit 0 (shared
box).
- Spec floors: `check-spec-range-floors.mjs` `analyze`, scoped to the
two packages over fresh `dist`, gave `findings 0`. Control: the same run
with both floors at `^17.0.0` gave `floor-too-low` naming
`resolveFieldScale` for both packages (3 findings; fields adds
`InlineGridColumn`).
- eslint over the 6 changed lintable files (`--format json`) gave 0
errors. Warnings on the four edited files equal BASE through `--stdin`:
`fields/src/index.tsx` 138/138, `ObjectGantt.tsx` 81/81,
`ObjectGantt.test.tsx` 30/30, `ObjectGantt.numberLocale.test.tsx` 5/5.
The new pins carry only `no-explicit-any` (2 and 6). Narrowing proof:
(i) the population is the 6 changed lintable files, and none is ignored
by `eslint.config.js`; (ii) the JSON output holds 6 results; (iii)
`eslint.config.js` sets no `parserOptions.project` or `projectService`,
so type-aware linting is off and this diff cannot move an untouched
file's verdict.
- Exit 0: `check-changeset-presence` (`6 source file(s) of 2 released
package(s) changed, and this change declares 2 changeset(s)`),
`changeset:check`, `check:control-bytes`, `check:new-line-citations`
(`VERDICT new-cross-file-line-citations: 0 new citation(s)`),
`check:pending-changeset-literals`, `check:phantom-deps`,
`check:unused-deps`, `check:spec-symbols`, `check:unreferenced-sources`,
`check:test-path-roots`, `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`, and
`check:changeset-claims` (report-only; see Acceptance notes).
- NOT MEASURED: the full-workspace `pnpm check:spec-floors` (reason: it
needs every package's `dist`, and only the two changed packages and
their closure were built; the scoped run and its control are above), and
the full lint and test farm (reason: CI owns it).
## Acceptance notes
- **The gantt tooltip's numeric row still ignores the grouping policy**
(the ordinal no-grouping rule for a declared `scale: 0`, objectui#4033,
and the authored `useGrouping`, objectui#11026). A `scale: 0` year reads
`2,026` in the tooltip and `2026` in the list cell. That was already so
before this PR (it read `2,026.00`). It is a different class from this
card's width, so it is left as is and recorded here. No first-party
`tooltipFields` producer over a `number` field was found. Carrier: none.
- The pending `.changeset/10071-number-scale-ceiling.md` says "In-range,
negative and non-integer widths are unchanged". That is true of the
change it describes (the retired clamp). It ships in the same release as
this PR's changeset, which says those widths now count as no declaration
in the number cell. The two read together without contradiction, but the
seat may add a dated note there if it judges the release text ambiguous.
Not edited here.
- The `scale` above 100 path is not changed: the resolver returns it
verbatim, and `FieldSchema` refuses it at authoring.
- Local install: the container runs node `v22.22.0`, and `jsdom@30.0.1`
declares `^22.22.2`. The root `.npmrc` sets `engine-strict=true`, so
this worktree was installed with `pnpm install
--config.engine-strict=false`. That is a one-off override on the command
line; no tracked file changed.
Implemented by the `domain:ui` seat 1 dev, branch
`claude/issue-11254-number-faces-resolver`, session
`https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 549aaa8 commit c1763e5
8 files changed
Lines changed: 369 additions & 12 deletions
File tree
- .changeset
- packages
- fields/src
- __tests__
- plugin-gantt/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
784 | 784 | | |
785 | 785 | | |
786 | 786 | | |
787 | | - | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
788 | 801 | | |
789 | 802 | | |
790 | 803 | | |
| |||
796 | 809 | | |
797 | 810 | | |
798 | 811 | | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
799 | 815 | | |
800 | 816 | | |
801 | 817 | | |
802 | 818 | | |
803 | 819 | | |
804 | | - | |
805 | | - | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
806 | 823 | | |
807 | 824 | | |
808 | 825 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | | - | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
158 | 165 | | |
159 | 166 | | |
160 | 167 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
141 | | - | |
142 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
143 | 147 | | |
144 | 148 | | |
145 | 149 | | |
| |||
0 commit comments