Commit 6fb7115
Fixes #21768
Clause-②: yes (widening)
## What this does
The spec half of objectstack-ai/objectui#11610, which renamed the `grid`
widget's eight field-level keys to camelCase (merge `2abec3a96c`). The
`.objectui-sha` pin on `main` is `9dfaca654311`, and it carries that
merge: `merge-base --is-ancestor 2abec3a96c 9dfaca654311` exits 0, which
proves ancestry on its own.
This executes ruling B on #21704 fork 2 (record `5978663135`). That
ruling deferred the eight keys "until objectui camelCases them". This PR
also answers objectui's two asks in `5981368783` and the at-tier
review's ③ in `5986881331`. It works under the claim `5987845581`.
| | before | after |
|:--|:--|:--|
| runtime form field (`buildObjectFormRuntimeField`: `object-form`
`customFields[]`, and the inline arm of both forms'
`sections[].fields[]`) | `minRows` … `sortField` refused as unrecognized
keys | declared with the widget's value types |
| the eight snake_case spellings | refused, with one shared
prescription: "these come in once the widget reads a camelCase spelling"
| still refused by name; each refusal names its own camelCase key |
| `totalField` | not declared | described as the grid's CHILD column
summed into the footer, not the PARENT field a master-detail or
`record:line_items` sum is saved to |
## Census at the pin (objectui `9dfaca654311`, read with `git show`)
| key | value type: `GridFieldMetadata`
(`types/src/field-types.ts:1031-1088`) and the zod mirror
(`types/src/zod/form.zod.ts:1102-1117`) | grid read
(`fields/src/widgets/GridField.tsx`, `cfg = field` at `:669`) |
snake_case spelling |
|:--|:--|:--|:--|
| `minRows` | `number` / `z.number()` | `:762`; Remove stops at it
(`:882`, `:1447`) | `min_rows`: refused |
| `maxRows` | `number` / `z.number()` | `:763`; Add, Duplicate and the
blank row stop at it (`:808`, `:850`, `:892`, `:1077`, `:1487`) |
`max_rows`: refused |
| `allowAdd` | `boolean` / `z.boolean()` | `:749`, `!== false`; off when
read-only or disabled | `allow_add`: refused |
| `allowDelete` | `boolean` / `z.boolean()` | `:750`, the same |
`allow_delete`: refused |
| `allowReorder` | `boolean` / `z.boolean()` | `:783`, the same |
`allow_reorder`: refused |
| `totalField` | `string` / `z.string()` | `:771`; summed into the
footer at `:919-923` | `total_field`: refused |
| `addLabel` | `string` / `z.string()` | `:1322` (empty state) and
`:1491` (Add button) | `add_label`: refused |
| `sortField` | `string` / `z.string()` | `:778`; stamped on every row
by `emit` (`:785-790`) | `sort_field`: refused |
- **No snake_case read remains.** `git grep` of the eight snake_case
spellings over objectui's `packages/*/src` (tests and stories excluded)
returns only its refusal faces:
- the TS tombstones (`field-types.ts:1106-1141`, `form.ts:2108-2143`);
- `GRID_FIELD_RETIRED_KEYS` (`field-types.ts:1159`);
- the zod alias refusals (`zod/form.zod.ts:1122-1129`);
- one comment in `GridField.tsx:617`.
The widget refuses a field that carries any of them, and draws a refusal
instead of the grid (`GridField.tsx:643-650`). The control grep for the
camelCase spellings over the same scope has 38 hits in `GridField.tsx`
alone.
- **`FormField` takes each key by reference** to `GridFieldMetadata`
(`types/src/form.ts:2079-2094`).
- **`totalField` is a homonym.** The line-items panel and the
master-detail form both hand the grid their `amountField` *as* its
`totalField` (`plugin-form/src/LineItemsPanel.tsx:710`,
`MasterDetailForm.tsx:876`).
## Writers of either spelling in this repo (base `75ddcd1b41`)
- **snake_case:** none authored.
- The only authored value is the spec pin's own refusal probe
(`component-form-custom-fields-sections-typed.pin.test.ts`, `min_rows`).
- Prose mentions: the S-forms changeset's FROM → TO row, the console pin
changeset's interim note, and dated comments in
`conversions/registry.ts`, `migrations/registry.ts` and two migration
entries.
- **camelCase on an inline form field:** none in `examples/`, `skills/`,
`content/docs/` or `apps/`.
- The `addLabel` hits there
(`examples/app-showcase/.../project-workspace.page.ts:59`,
`skills/objectstack-ui/SKILL.md:83`) are master-detail detail entries, a
different surface that is unchanged.
- The objectui writer this PR pins is the schema catalog's
`fields-grid/line-items-grid` field, which carries all eight keys.
## Changes, `packages/spec/src/ui/component.zod.ts`
- **The eight members.** A new builder,
`objectFormRuntimeFieldGridMembers()`, declares them, and the runtime
field spreads it into its shape. The builder's docblock carries the read
points above.
- **The refusal map.** `OBJECT_FORM_GRID_WIDGET_SNAKE_KEYS` is now a map
from each snake_case key to its camelCase key (`satisfies` the declared
key union, so a value naming an undeclared key fails `tsc`).
- **One guidance set per entry, under the map's name.** A set answers
once per message, so this way each written spelling gets its own bullet
naming its own replacement. The `total_field` bullet also restates the
CHILD-column meaning.
- **The three comments moved:**
- the line-items guidance block, re-measured at `9dfaca654311`:
`LineItemsPanel.tsx:696-720`, `:816-827`, spelled `addLabel` /
`sortField`;
- the `customFields` fork note, which now records the rename;
- the master-detail `sortField` retirement note, which keeps its dated
read and adds a hop sentence: the derived field is handed to the grid as
`sortField` at `MasterDetailForm.tsx:877`, and the entry key is still a
tombstone.
- **One runtime string had to change, because this PR makes it false.**
`record:line_items`' `sortField` refusal said "No block takes an
authored `sortField`". An inline `grid` field now takes one, for the
rows of its own value, so the sentence now reads "No block takes an
authored `sortField` for child records". The accept set is unchanged.
`content/docs/references/ui/component.mdx` was regenerated by
`check:generated --fix`, which named only `gen:docs` as stale. The
changeset is
`.changeset/21768-object-form-runtime-field-grid-camelcase-keys.md`:
`@objectstack/spec` minor, with `Clause-②: yes (widening)` at line
start.
## D3 reading
No accept set narrows. Every value that parsed at the base still parses,
so no ADR-0087 D3 entry and no conversion is owed. Before this PR, the
eight camelCase keys were refused (`unrecognized_keys`) and the eight
snake_case keys were refused. After it, the camelCase keys parse and the
snake_case keys are still refused. A camelCase key with the wrong value
type is still refused, now as `invalid_type`. No step-18 file is
touched.
## Tests, at head `bb32836dc9`
**`component-form-custom-fields-sections-typed.pin.test.ts`**
- **§1:** the catalog writer parses byte-identical as a `customFields`
member. Lit controls: the keys on an `object-form` section's inline
entry, with the three switches `false`, and on an
`object-master-detail-form` section's inline entry.
- **§2:** the existing `min_rows` row keeps its code and path. Its
prescription check now asserts that the bullet names `min_rows` and
`minRows`.
- **§3:** the key-set pin gains the eight keys.
- **§5** (new):
- each snake_case key gives `unrecognized_keys` at `customFields.0`, and
exactly one bullet names that key and its camelCase key. The camelCase
key then parses with the same value.
- two retired keys on one field give two bullets, one each.
- a section's inline entry refuses `total_field` the same way.
- each camelCase key refuses a wrong value type as `invalid_type` at its
own path.
- each describe names its one reader.
- the `totalField` homonym: the inline grid field's describe starts with
the CHILD column and the footer, and names `amountField`. The
`record:line_items` and master-detail detail-entry describes start with
"Parent field to receive the rolled-up sum" and never say CHILD or
footer.
The assertions check named subjects, not copy.
**`master-detail-detail-sort-field-retirement.test.ts`**
- Its tree-scoped absence walk flagged the new pin's `sortField:
'position'`. The walk's own header covers this case: "A future schema
that declares a `sortField` of its own would trip this walk: narrow the
matcher to detail entries then, never exclude the new file."
- So the matcher is narrowed, and no file is excluded. A match is
skipped only when the object literal around it names `type` or `widget`
`grid` / `field:grid` at its own level, which marks the inline grid
field.
- Three anti-vacuity probes stay offenders: a nested column's `grid`
type, an inline grid field placed earlier than a detail entry in the
same text, and `type: 'grids'`.
- The six pre-existing probes are unchanged.
**Ablation**
- **Prediction, written before the run:** dropping `sortField` from the
builder turns six tests red. They are §3's key set, §1's two grid writer
rows, §5's `sort_field` refusal row, §5's `sortField` value-type row,
and §5's describes test. The retirement pin stays green.
- **Method:** `node scripts/ablation-replace.mjs --delete`. The anchor
hit once and went 1 → 0, and the blob went `298601db7467` →
`48deefce4054`.
- **Result:** exactly those six went red, `Tests 6 failed | 108 passed
(114)`.
- **Restore:** the blob matches HEAD `298601db7467`, `git diff HEAD` is
empty, and `git status --porcelain` is empty.
- **Two runs:** at `edbdef7bf8`, and again at `bb32836dc9`. The subject
is imported from `./component.zod` (source, not `dist`), so no rebuild
is involved.
**Suites**
- `pnpm --filter @objectstack/spec build && pnpm --filter
@objectstack/spec typecheck` at `bb32836dc9`: exit 0.
- `check:test-typecheck` says OK, and the debt ledger is unchanged.
- `tsc -p tsconfig.test.json --listFilesOnly` lists both edited test
files, and neither has a ledger entry.
- The full spec suite (`vitest run --project local`): `Test Files 615
passed (615)`, `Tests 18383 passed | 1 todo`. It ran at `edbdef7bf8`.
The only later change is the pin file's assertion wording, re-run at
`bb32836dc9` with `alias-integrity.test.ts` and `strict-object.test.ts`:
`Tests 166 passed (166)`.
## Gates, at head `bb32836dc9`
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 110 commands for this change set,
and every one exits 0.
- `--ran` reconciliation: `110 derived, 110 run, 0 NOT-MEASURED, 0
UNRUN`.
- Exit codes were captured before any pipe.
- `check:type-check-debt` ran under the verify lock, because its first
unlocked run hit the 280s per-command cap.
- At the first head (`edbdef7bf8`), six gates
(`check:doc-formula-expressions`, `check:doc-security-posture`,
`check:skill-examples`, `check:docs-transcript-drift`,
`check:lean-entry-closure` and `check:dual-build-cjs-loads`) first
answered exit 3, PREREQUISITE NOT MET, because their packages had no
`dist`. After a turbo build of `lint`, `formula`, `client-react` and
`objectql` (all cache hits), each answered 0. All six also answered 0 at
`bb32836dc9`.
- **Lint, a measured narrowing.** Repo-wide `pnpm lint` is CI's.
Locally, ESLint ran over the three edited TS files at `bb32836dc9`, with
three pieces of evidence:
- **Population:** each file resolves a config through `--print-config`
(`packages/**/*.{ts,tsx,mts,cts}`), so none is ignored.
- **Count:** `--format json` reports 3 files, 0 errors and 0 warnings.
- **Invariance:** the resolved `parserOptions` holds only `ecmaVersion`
and `sourceType`, with no `project`, and no typed rule is on. Linting is
not type-aware, so this diff cannot change the verdict on any file it
did not touch.
- **Not measured locally:** the path-scheduled CI jobs and type-check
lanes that `dispatch-gates` lists outside its derived set (Test Core
shards, Dogfood, Build Core, Build Docs, Temporal Conformance, and the
workspace and consumer type checks). CI runs them.
## Declared beyond the claim's listed surface
- `master-detail-detail-sort-field-retirement.test.ts`: the claim lists
no file for this edit. The new `sortField` member is what tripped the
walk, and the walk's header says what to do, so it was narrowed, not
excluded (see Tests).
- The `record:line_items` `sortField` refusal sentence: the sentence
became false with this PR, so it was corrected in the same file (see
Changes).
## Acceptance notes
- `main` is 2 commits past this branch's base (`ba57588665`). They touch
`service-settings`, the QA checklist JSON and a comment-only hunk in
`spec/src/contracts/crypto-provider.ts`. None of that overlaps this
diff, so `main` was not merged in. CI's merge ref covers the
combination.
- Three dated prose records still describe the pre-rename spelling or
the old reach. All are accurate as dated reads, and nothing parses them.
Carrier: none.
- The `replacement` text of the D3 entry
`ui-record-line-items-props-closed` says "`sortField`, which no block
takes".
- `conversions/registry.ts` (about `:12488`) and the retired-key entry
`18.ui__ObjectMasterDetailFormProps__details.sortField` say the derived
field is "handed to the grid as `sort_field` (`:874`)", read at pin
`89cad75d5570`.
- The S-forms D3 `reason` says the snake_case keys stay out "until the
widget reads a camelCase" spelling.
- The pending S-forms changeset
(`.changeset/21464-component-props-form-custom-fields-sections-typed.md`)
still says the eight keys "come in once the widget reads a camelCase
spelling". If it ships in the same release as this changeset, the
CHANGELOG reads as a sequence: that entry, then this one stating that
they came in. It is not edited here, because it is outside this PR's
surface. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 53021e3 commit 6fb7115
5 files changed
Lines changed: 370 additions & 33 deletions
File tree
- .changeset
- content/docs/references/ui
- packages/spec/src/ui
Lines changed: 32 additions & 0 deletions
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
657 | 657 | | |
658 | 658 | | |
659 | 659 | | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
660 | 668 | | |
661 | 669 | | |
662 | 670 | | |
| |||
Lines changed: 127 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
36 | 46 | | |
37 | 47 | | |
38 | 48 | | |
| |||
46 | 56 | | |
47 | 57 | | |
48 | 58 | | |
| 59 | + | |
49 | 60 | | |
50 | 61 | | |
51 | 62 | | |
| |||
201 | 212 | | |
202 | 213 | | |
203 | 214 | | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
204 | 238 | | |
205 | 239 | | |
206 | 240 | | |
| |||
342 | 376 | | |
343 | 377 | | |
344 | 378 | | |
345 | | - | |
| 379 | + | |
346 | 380 | | |
347 | 381 | | |
348 | 382 | | |
| |||
381 | 415 | | |
382 | 416 | | |
383 | 417 | | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
388 | 423 | | |
389 | 424 | | |
390 | 425 | | |
| |||
453 | 488 | | |
454 | 489 | | |
455 | 490 | | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
0 commit comments