Repository navigation
Commit d78a0bd
docs(spec): field.useGrouping is live at the objectui pin, and its docblock describes the heuristic the renderer uses (#20908)
Fixes #20313
Clause-②: no
`field.useGrouping` moves `planned` → `live` in the liveness ledger,
citing the objectui reader at this repo's `.objectui-sha` pin, and the
key's JSDoc stops describing a grouping heuristic the renderer does not
use. Six files, the claim's surface as the seat amended it for the patch
round (claim `5915464335`; see *Patch round* below):
- `packages/spec/liveness/field.json`: the `useGrouping` row only.
`status: live`, `verifiedAt: 2026-09-30`, `evidenceScope: cross-repo`,
`evidence` = the reader, `producer` = the seven relay sites, and a
rewritten `note` that no longer says the consumer is in flight.
- `packages/spec/liveness/state-counts/field.md`: regenerated by `pnpm
--filter @objectstack/spec gen:liveness-counts`, `live 91 → 92`,
`planned 2 → 1`.
- `packages/spec/src/data/field.zod.ts`: the JSDoc above `useGrouping`
only. The `.describe()` string was true and is unchanged, so no
generated file moves (`check:generated`: all 15 artifacts up to date).
- `.changeset/20313-use-grouping-live.md`: one `@objectstack/spec`
patch. The ledger and `src/**/*.zod.ts` both ship in the tarball. The
two test files below do not ship, so the changeset is unchanged by the
patch round.
- `packages/spec/scripts/liveness/check-liveness.test.ts` (patch round):
the planned missing-file case makes its own `planned` row, and the typo
fixture's comment says what holds.
- `packages/spec/src/data/field.test.ts` (patch round): the comment at
`:843-846` stops repeating the false closing sentence.
## Reader citations: objectui at the pin
`db11afd4967cd9d39381c5e21dc2deec9d706204`
The pin carries the landing. REST `compare ae582b70a...db11afd4967c` on
objectui answers `ahead`, `ahead_by 21`, `behind_by 0` (read
2026-09-30). The local objectui clone is shallow and lacks `ae582b70a`,
so no local ancestry reading is claimed.
| ledger field | objectui `file#symbol` | line at the pin | what it does
|
|:--|:--|:--|:--|
| `evidence` |
`packages/core/src/utils/number-display.ts#shouldGroupDisplayNumber` |
`:200` (authored boolean returns `:209`, currency `:212`, `scale !== 0`
`:214`) | the one grouping decision; an authored boolean answers first |
| `evidence` |
`packages/core/src/utils/number-display.ts#formatDisplayNumber` | `:227`
(`:263-266`) | spells the decision for `Intl.NumberFormat`:
`useGrouping: false`, or `true` when authored `true` |
| `evidence` | `packages/fields/src/index.tsx#NumberCellRenderer` |
`:770` (reads `:810-812`, passes `:819`) | reads `field.useGrouping`,
booleans only |
| `producer` | `packages/plugin-grid/src/ObjectGrid.tsx#generateColumns`
| `:3357`, `:3586`, `:3761` | copies it beside `scale` on all three
column paths |
| `producer` |
`packages/plugin-detail/src/fieldEnrichment.ts#ENRICHED_FIELD_METADATA_KEYS`
| `:208` | in the key list copied onto a detail-view field |
| `producer` |
`packages/plugin-detail/src/RecordDetailPanel.tsx#buildRecordDetailFields`
| `:171` | relays it beside `scale` |
| `producer` |
`packages/plugin-detail/src/RelatedList.tsx#effectiveColumns` | `:1562`
| relays it beside `scale` |
| `producer` |
`packages/plugin-list/src/ObjectGallery.tsx#ObjectGallery` | `:376` |
relays it beside `scale` |
These are the landing note's (`5890301145`) readers and seven relay
sites, each re-located at the pin. The relay sites go in `producer` per
the README's table: the cell reads field metadata those sites build,
never the object definition. The gate attributes every one of these
paths to objectui, so it counts them and never resolves them.
`scanEvidence` on the new row returns zero local paths: 2 foreign in
`evidence`, 5 in `producer`.
## The docblock: what was false at the pin, and what it says now
The dispatch named two false sentences. A third one was false too, and
it is changed by tense only.
1. **The heuristic.** It said `scale: 0` with no upper bound keeps
grouping and that a small bounded integer range drops it. The pin's
heuristic never reads a bound: a declared `scale: 0` drops grouping, and
any other `scale`, or none, keeps it. It now says so, keeping
`objectui#4033`, which answers 200 and is the issue that introduced the
heuristic.
2. **The closing sentence.** It said no default is declared "until the
renderer half of this contract (objectui#4033) lands and retires the
interim heuristic". The renderer half landed as objectui `ae582b70a`,
and it keeps the heuristic for an absent key rather than retiring it.
The sentence now says that and names objectui's
`shouldGroupDisplayNumber`. No tracker number is added.
3. **The motivation sentence** said console renderers "construct
`Intl.NumberFormat` with grouping unconditionally ON", so a `scale: 0`
year "renders `2,026` everywhere it is shown". That is false at the pin,
where the same field renders `2026`. Only the three verbs change:
constructed, rendered, was shown.
The rest of the docblock is untouched, its other tracker numbers
included (#20234 owns those).
## Controls
Every reading below is objectstack at this branch, base `33b6e8bece`,
and objectui at the pin.
- **Before the flip** (`33b6e8bece`, untouched tree): `pnpm --filter
@objectstack/spec check:liveness` exit 0, `field 94 classified (live 91,
planned 2, dead 1)`, total `967 live · 21 planned`.
- **Flip, before the regeneration:** exit 1,
`packages/spec/liveness/state-counts/field.md is STALE`.
- **After** (`b842de02e0`): exit 0, `field 94 classified (live 92,
planned 1, dead 1)`, total `968 live · 20 planned`. The count moved by
exactly one. The success line still claims every repo-local evidence
path resolves (664/664). Attributed-to-another-repo count 228 → 230,
rows citing a producer 118 → 119, cross-repo scope 142 → 143,
`verifiedAt` 652 → 653.
- **Ablation** (`node scripts/ablation-replace.mjs`, wrap mode, over the
committed `b842de02e0`): the anchor `; objectui:
packages/fields/src/index.tsx#NumberCellRenderer` becomes `;
packages/fields/src/index.tsx#NumberCellRenderer`. That drops the realm
marker, which is the realistic way this row rots. The anchor count went
1 → 0 and the blob went `ec2da038770a → 40c4a5c2ec52`. `check:liveness`
exited 1 with `✗ 1 'live' / 'planned' / 'experimental' /
'live-elsewhere' entr(ies) cite a file that is missing from THIS repo:
field/useGrouping → packages/fields/src/index.tsx`. Restored: blob after
= blob at HEAD = `ec2da038770a`, `git diff HEAD` empty, `git status`
clean. A re-run is exit 0 with `live 92, planned 1`.
## Tests and gates, first round (at `b842de02e0`)
- `pnpm --filter @objectstack/spec build`: exit 0, and it moved no
tracked file.
- `check:generated`: exit 0, all 15 artifacts up to date.
- spec `vitest --project local`: **582 files / 17161 tests passed** (1
todo).
- spec `test:repo`: **46 files / 813 tests passed**.
- spec `typecheck` (tsc, scripts, test layer): exit 0.
- `node scripts/pm/dispatch-gates.mjs` derived 80 commands, all run,
exit codes captured before any pipe. `--ran` reconciliation: `80
derived, 79 run, 1 NOT-MEASURED, 0 UNRUN`. The exceptions:
- `check:dual-build-cjs-loads`: exit 3, **NOT MEASURED** (`PREREQUISITE
NOT MET`: it needs the built output of 86 packages, and this worktree
built the spec, formula, lint and objectql closures only). CI builds the
whole tree.
- `check:platform-checklist`: exit 1 with 2 problems, `coverage.json ·
picklist: UNCLASSIFIED` and `areas/identity-auth.json …
auth-plugin.ts#twoFactor`. **Pre-existing:** the same run on a detached
checkout of the base `33b6e8bece` exits 1 with the identical two
problems. Neither is touched here.
- `check:doc-formula-expressions` and `check:lean-entry-closure` first
exited 3 (prerequisites not built). After `turbo run build` of the
formula, lint and objectql closures, both exit 0.
- **Lint, a declared narrowing:** eslint `--no-inline-config --format
json` over the 4 changed files returns 4 results. `field.zod.ts` has 0
errors and 0 warnings. For the other three, eslint's own output is "File
ignored because no matching configuration was supplied".
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules), so this diff cannot move the
verdict on any untouched file.
## Patch round (the seat's ruling on the report's two open questions)
Head `f06c26f536`. It holds three commits after `b842de02e0`:
- `8c1fa09815`, the patch itself.
- `1608b8857f`, a merge of `origin/main` at `9509ea106a`.
`dispatch-gates` flagged the tree as stale on a family file
(`scripts/pm/check-governed-queue-guard.mjs`), so main was merged. The
merge brings nothing into `packages/spec`: `git diff 8c1fa09
1608b88 -- packages/spec` is empty.
- `f06c26f536`, a three-line correction to one comment.
**`check-liveness.test.ts`, the planned missing-file case
(`:403-426`).** It no longer relies on the shipped ledger grading
`field.useGrouping` `planned`. The case now does four things in the
copy:
1. sets the copied row to `planned`;
2. moves the copy's count shard with it, using the file's existing
`moveCount`;
3. writes the rotted pointer;
4. reads the row back as its control:
`expect(readRow(...).status).toBe('planned')`.
`setStatus` moves verbatim from the typo block to module scope, and
`readRow` is new.
- **The copied row keeps its `producer`, `evidenceScope` and
`verifiedAt`,** because none of them trips anything on a `planned` row.
Measured on a scratch copy:
- status `planned` alone: exit 1, and the only finding is
`state-counts/field.md is STALE`;
- status `planned` plus the count move: exit 0.
So the rotted pointer stays the run's only cause.
- **Control: an ablation** through `scripts/ablation-replace.mjs
--delete`, wrap mode, over the committed `8c1fa09815`.
- It deleted `setStatus(root, 'field', 'useGrouping', 'planned');`
(anchor 1 → 0, blob `06861b68122f → 27ad6cf2ec7b`).
- The case went red on its control, `AssertionError: expected 'live' to
be 'planned'`, in 19 ms and before the gate was spawned. The `live` and
`experimental` siblings stayed green.
- Restored: blob = HEAD `06861b68122f`, and `git diff HEAD` is empty.
- Unmutated, the three cases pass by name.
- The mutation is in the test file itself, which vitest runs from
source, so there is no `dist/` leg.
- `f06c26f536` changes only a comment in another block of this file.
**The typo fixture's comment (`:934-939`)** now states what holds. This
was read from the gate, not assumed:
- `check-liveness.mts:886` scans `evidence` only for a status in
`EVIDENCE_SCANNED_STATUSES`, and `planed` is not one of them.
- `:880-885` resolves `producer` at any status, but every path the row's
`producer` cites is objectui-attributed. `scanEvidence` returns zero
local paths, so none is resolved against this checkout.
- `producer.mts` fails `producer` or `evidenceScope` only when the value
is malformed.
**`field.test.ts:843-846`** now says the renderer half keeps the interim
heuristic for an absent key, and names objectui's
`shouldGroupDisplayNumber`. It is the same wording class as JSDoc
sentence (b).
**The changeset is unchanged.** Neither test file ships: `files[]`
carries `src/**/*.zod.ts`, never `*.test.ts`, and nothing under
`scripts/`.
**Readings.** At `f06c26f536` unless noted:
- `check:liveness`: exit 0, `field 94 classified (live 92, planned 1,
dead 1)`.
- The two changed test files: 2 files / 332 tests passed. The same count
also at `8c1fa09815` and `1608b8857f`.
- spec `typecheck`: exit 0. The test-layer ledger is unchanged at 52
files / 249 errors / 137 signatures.
- spec `test:repo`: 46 files / 813 tests passed at `1608b8857f` and at
`8c1fa09815`. It was not re-run at `f06c26f536`: that commit changes
only three comment lines in a `local`-project test file.
- spec `vitest --project local`: 582 files / 17161 tests passed at
`8c1fa09815`. The spec tree is byte-identical at `1608b8857f`.
- Gates: `dispatch-gates --commands` derived 87 commands. The test-file
kind adds `check:engine-double-contract`, `check:objectql-double-limit`,
`check:pm-governed-merges`, `check:query-options-erasure`,
`check:type-check-coverage`, `check:type-check-debt` and
`check:where-matcher`. All 87 were run after a full `turbo run build` of
`./packages/*` and `./packages/*/*`, 71/71 tasks.
- `--ran` reconciliation: `87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN`.
- 86 exited 0, including `check:dual-build-cjs-loads` (not measured in
the first round) and `check:type-check-debt` ("none above its recorded
number").
- `check:platform-checklist` exits 1 with the same two pre-existing
problems measured on the base in the first round.
- Lint: eslint `--no-inline-config --format json` over the 6 changed
files. The 3 it lints (`check-liveness.test.ts`, `field.test.ts`,
`field.zod.ts`) have 0 errors and 0 warnings. The other 3 report "no
matching configuration". Type-aware linting is never enabled.
## Acceptance notes
Findings outside the claim's file surface as first delivered. Two are
folded into the patch round; the rest are untouched.
1. **Folded in the patch round:** the `check-liveness.test.ts` fixtures,
which this flip had left driving a `live` row. See *Patch round*.
2.
**`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts:387-393`**,
the `field` / `useGrouping` omit row, says "declared, not enforced yet —
liveness verdict `planned` (the renderer read side … is not landed)". It
is stale after this flip, by the file's own rule at `:307-309`: once the
key is enforced, "delete it and decide the offer then — that decision
belongs to the enforcement". The seat ruled the offer: offer it.
**Carrier: the seat's follow-up card, filed at ACCEPT.** That card
deletes the omit row, adds a `useGrouping` row on `field.form.ts` for
the number type, and regenerates the catalogs the form rows feed.
Neither file is touched here.
3. **Folded in the patch round:** `packages/spec/src/data/field.test.ts`
repeated the false closing sentence. See *Patch round*.
4. **objectui `packages/plugin-form`** (`deriveMasterDetail.ts:271`,
`:323`; `sectionFields.ts:409`) copies `scale` without `useGrouping`.
This is a read-only inference, not reproduced, and it may be only an
input widget. The ledger's CAVEAT limits the `live` claim to the
surfaces `producer` names. Carrier: none.
5. **The one-word rider offered in `5915472266`** ("supplies" at
`field.zod.ts` 2649-2650) is not taken, because it is outside the
claim's surface. Its pointer names #20827 as the carrier.
---
_Generated by [Claude
Code](https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4d0b9cd commit d78a0bd
6 files changed
Lines changed: 70 additions & 27 deletions
File tree
- .changeset
- packages/spec
- liveness
- state-counts
- scripts/liveness
- src/data
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
197 | | - | |
198 | | - | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
199 | 203 | | |
200 | 204 | | |
201 | 205 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
76 | 89 | | |
77 | 90 | | |
78 | 91 | | |
| |||
388 | 401 | | |
389 | 402 | | |
390 | 403 | | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
395 | 413 | | |
396 | 414 | | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
397 | 418 | | |
| 419 | + | |
| 420 | + | |
398 | 421 | | |
399 | 422 | | |
400 | 423 | | |
| |||
904 | 927 | | |
905 | 928 | | |
906 | 929 | | |
907 | | - | |
908 | | - | |
909 | | - | |
910 | | - | |
911 | | - | |
912 | | - | |
913 | | - | |
914 | | - | |
915 | 930 | | |
916 | 931 | | |
917 | 932 | | |
918 | 933 | | |
919 | | - | |
920 | | - | |
921 | | - | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
922 | 940 | | |
923 | 941 | | |
924 | 942 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
841 | 841 | | |
842 | 842 | | |
843 | 843 | | |
844 | | - | |
845 | | - | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
846 | 847 | | |
847 | 848 | | |
848 | 849 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1277 | 1277 | | |
1278 | 1278 | | |
1279 | 1279 | | |
1280 | | - | |
| 1280 | + | |
1281 | 1281 | | |
1282 | 1282 | | |
1283 | | - | |
| 1283 | + | |
1284 | 1284 | | |
1285 | 1285 | | |
1286 | 1286 | | |
| |||
1289 | 1289 | | |
1290 | 1290 | | |
1291 | 1291 | | |
1292 | | - | |
1293 | | - | |
1294 | | - | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
1295 | 1296 | | |
1296 | 1297 | | |
1297 | 1298 | | |
| |||
1308 | 1309 | | |
1309 | 1310 | | |
1310 | 1311 | | |
1311 | | - | |
1312 | | - | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
1313 | 1315 | | |
1314 | 1316 | | |
1315 | 1317 | | |
| |||
0 commit comments