Repository navigation
Commit 31c3996
feat(spec): offer useGrouping on the number field form, and delete its stale not-enforced-yet omit row (#20934)
Fixes #20920
Clause-②: no
## What
`field.useGrouping` is `live` since `d78a0bda07`, the enforcement
carried by #20313: objectui's `shouldGroupDisplayNumber` answers an
authored value first. The reconciliation ledger's own rule assigns the
offer decision to that enforcement ("Once a key is enforced its row is
stale: delete it and decide the offer then"), and its seat ruled **offer
it** (`5916202706`, corrected by `5916211005`). The at-tier review
`5917470646` on PR #20908 routed the move to this card. This PR carries
it out:
- `metadata-form-zod-reconciliation.test.ts`: the `field/useGrouping`
"declared, not enforced yet" omit row is deleted, not re-reasoned.
- `field.form.ts`: one `useGrouping` row beside `scale`, with `scale`'s
gate `visibleWhen: "data.type == 'number'"`.
- The four `platform-objects` metadata-form catalogs, regenerated with
the generator. The two new leaves are authored in `zh-CN`, `ja-JP` and
`es-ES`.
- Two population pins that the row moves mechanically, and one
changeset.
| key | form, section | control | the row it copies | gate |
|:--|:--|:--|:--|:--|
| `field.useGrouping` | `field.form.ts`, Configuration | plain row, no
`widget`, no `type` | `field.form.ts` `allowCreate` (the same
`z.boolean().optional()` node, no default) | `number` (`scale`'s) |
The help text follows the key's `.describe()`: "Digit grouping
(thousands separators) in displayed values. Unset: the renderer decides;
an untouched switch writes nothing, so it reads off even where the
renderer groups. Off: never grouped, e.g. a year or an ID. On: always
grouped." The middle clause restates, for this switch, the sentence the
object form's `userActions` row carries for the same renderer behaviour
(see Acceptance notes).
No schema accept set moves and no export changes: `check:api-surface`
reports the surface unchanged, and `check:authorable-surface` exits 0.
## The control (dispatch assumption 1)
| commit | tree | `metadata-form-zod-reconciliation.test.ts` |
|:--|:--|:--|
| `699517cfb9` | omit row deleted, no form row | **red**: `field.(root):
accepted by the Zod but unauthorable in the form — offer it, or add a
root ledger entry that records why it is not offered: expected [
'useGrouping' ] to deeply equal []`. Tests 1 failed, 75 passed (76).
`VERDICT command-exit 1` |
| `75773930ed` | form row added | **green**: Tests 76 passed (76).
`VERDICT command-exit 0` |
At the final head the same file is green inside the full
`@objectstack/spec` run below.
## The console renders it (dispatch assumption 4)
Read in objectui at the pin `db11afd4967c`, source only, no browser:
- The served node is `{ "type": "boolean", "description": … }`
(`packages/spec/json-schema/data/Field.json`).
- `SchemaForm.tsx` `inferWidget`: the row has no `widget` and no `type`,
so the JSON Schema branch runs, and `type === 'boolean'` gives
`'switch'`. No name detector fires: `useGrouping` admits no string, so
the condition, secret, icon, colour and field-ref detectors all decline.
- `resolveFieldFace`: `'switch'` is not a `WIDGETS` key but is in
`KNOWN_PASSTHROUGH_WIDGETS`, so the scalar chain runs, and
`effectiveType === 'boolean'` gives `{ kind: 'scalar' }`.
- `FieldControl`'s boolean branch renders a `Switch` with
`checked={!!value}` and writes only from `onCheckedChange`. Nothing is
written on mount.
- objectui's own `SchemaForm.requiredMarker-10367.test.tsx` renders a
bare `{ type: 'boolean' }` property as `role="switch"` with no wiring.
The field form's `allowCreate` row takes the same path today.
So the row renders with no extra wiring, and the offer is not the
ADR-0078 shape in reverse.
## Catalogs (dispatch assumption 3)
1. `node scripts/check-i18n-bundles.mjs --write`, the regenerator
AGENTS.md names for all nine bundle sets. Only `platform-objects` moved:
the `en` pair, English fills in the three translated locales, and two
provenance rows per locale. That output is committed unedited as
`b9a8778e0b`.
2. The six translated leaves are written by hand in this catalog's
authored vocabulary: "Unset:" is 未设置 / 未設定の場合は / Sin definir, "renderer"
is 渲染端 / レンダラー / renderizador, and the untouched-switch clause follows
each locale's authored `object.fields.userActions.helpText`. Labels:
千位分隔符 / 桁区切り / Separador de miles.
3. `--write` again. Merge mode kept every value and dropped the
provenance rows, so the three `*.source-hashes.generated.ts` files are
byte-identical to the base (`7fea903c24`). This is the rule `5757463712`
and `f4ce10c89d` read: an extractor fill is a byte copy recorded in the
provenance table, and a translated leaf is authored.
## Pins this PR moves
Each went red at exactly the predicted number first, and then moved:
- `packages/lint/src/validate-predicate-path-refs.test.ts`: the
shipped-form predicate census 82 → 83, and its literal-comparison count
57 → 58. Measured against the merge base `d78a0bda07`: the shipped
`*.form.ts` corpus adds exactly `field :: useGrouping :: data.type ==
'number'` and removes none.
-
`packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts`:
the translated-label positive control 658 → 659, one row label authored
in all three locales.
`field-panel-echo-decisions.test.ts` did not move: its no-echo rule
reads the two new leaves as authored. Every changed file is inside the
claim's file surface.
## Changeset
`.changeset/20920-use-grouping-form-row.md`: `@objectstack/spec`
**minor** and `@objectstack/platform-objects` **patch**, `Clause-②: no`.
These are PR #20428's levels for the same shape (form rows plus their
catalog leaves). What ships was measured after the build, with a
positive control each time:
- spec: the new row's text is in `dist/data/index.mjs`,
`dist/system/index.js` and their `dist/browser/` twins (`dist` is in
`files[]`), beside the control `Number of decimal digits`. The served
form payload gains an authoring control, which is a backwards-compatible
addition, so the level is minor.
- platform-objects: after a rebuild, the new leaves are in
`dist/plugin.mjs`, `dist/index.mjs` and
`dist/metadata-translations/index.mjs` (`Separador de miles` literally,
the zh-CN label as escapes), beside the control `Decimales`. This is
catalog content only, so the level is patch.
- No accept or refuse outcome moves, so the declaration is `no`.
## Verification, head `841a8734bd`
Every build, test and typecheck ran through
`scripts/pm/os-verify-lock.sh`. Each reported `VERDICT command-exit 0`,
except the three first-red measurements named above.
| run | result |
|:--|:--|
| `pnpm build` (whole workspace) | 72 of 72 tasks successful |
| `pnpm --filter @objectstack/spec test` | 583 files passed · 17166
tests passed, 1 todo |
| `pnpm --filter @objectstack/spec test:repo` | 47 files passed · 831
tests passed |
| `pnpm --filter @objectstack/platform-objects test` | 59 files passed ·
948 tests passed |
| `pnpm --filter @objectstack/lint test` | 117 files passed · 5438 tests
passed |
| spec / platform-objects / lint `typecheck` | exit 0 each.
`check:test-typecheck` OK at 52/248/136, 1/3/2 and 2/6/2
files/errors/signatures held |
| the form-registry readers in other packages: cli unit `i18n-coverage`
+ `i18n-duplicate-demand`; metadata-protocol `protocol.meta-types-*` (4
files); objectql `protocol-meta-types-rich` | 2 files / 27 tests, 4 /
58, 1 / 5, all passed |
| `pnpm --filter @objectstack/spec check:generated` | all 15 generated
artifacts up to date |
| `pnpm check:i18n` | OK: 9 packages, all bundles in sync, no undeclared
authoring keys |
| `pnpm check:i18n-stale-fill` | OK: 0 stale-fill leaves, 0 baselined |
**Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 86 commands at `841a8734bd`. All 86
ran, and each exit code was written to disk before any pipe. `--ran`
reports: 86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN, and all 86 exited
0.
**Lint, narrowed.** The 8 changed `.ts` files, each matched by eslint's
own config (`--print-config` exits 0 for each). The `--format json`
output reads 8 files, 0 errors, 0 warnings. Invariance:
`eslint.config.mjs` enables no type-aware linting (no
`parserOptions.project`), so this diff cannot move the verdict on a file
it does not touch. The repo-wide `pnpm lint` is CI's run.
No ablation was run: this PR adds a row and moves population pins, and
adds no guard. The reconciliation red/green pair above is the
measurement of the delta.
`origin/main` is three commits past this branch's base (`67c1b11a20`,
`aaad682dbc`, `87847a27ce`). None of them touches a file in this diff,
so it was not merged in.
## Acceptance notes
- **A two-state control on a three-state key.** The switch cannot show
"unset", and cannot put a touched field back to unset: once toggled, an
author reaches unset again only in the Source tab. The help text states
that the untouched state reads off. The same bound holds for every
switch over an optional boolean whose absence is not `false`, the object
form's `userActions` entries among them. Nothing is refused or dropped,
so no card is owed. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1571aed commit 31c3996
9 files changed
Lines changed: 49 additions & 10 deletions
File tree
- .changeset
- packages
- lint/src
- platform-objects/src/apps/translations
- spec/src
- data
- system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
614 | 614 | | |
615 | 615 | | |
616 | 616 | | |
617 | | - | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
618 | 624 | | |
619 | 625 | | |
620 | 626 | | |
| |||
713 | 719 | | |
714 | 720 | | |
715 | 721 | | |
716 | | - | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
717 | 725 | | |
718 | 726 | | |
719 | 727 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
580 | 580 | | |
581 | 581 | | |
582 | 582 | | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
583 | 587 | | |
584 | 588 | | |
585 | 589 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
580 | 580 | | |
581 | 581 | | |
582 | 582 | | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
583 | 587 | | |
584 | 588 | | |
585 | 589 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
580 | 580 | | |
581 | 581 | | |
582 | 582 | | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
583 | 587 | | |
584 | 588 | | |
585 | 589 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1148 | 1148 | | |
1149 | 1149 | | |
1150 | 1150 | | |
1151 | | - | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
1152 | 1154 | | |
1153 | 1155 | | |
1154 | 1156 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
580 | 580 | | |
581 | 581 | | |
582 | 582 | | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
583 | 587 | | |
584 | 588 | | |
585 | 589 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
85 | 95 | | |
86 | 96 | | |
87 | 97 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
384 | 384 | | |
385 | 385 | | |
386 | 386 | | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | | - | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | 387 | | |
395 | 388 | | |
396 | 389 | | |
| |||
0 commit comments