You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6ec54f0
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec)!: retire the object-master-detail-form detail entry sortField — the console derives the line-position field from the child object (#21589) (#21632)
Fixes#21589
Clause-②: no (narrowing)
## What this does
Retires `sortField` from the `object-master-detail-form` detail entry
(`ComponentPropsMap['object-master-detail-form'].details[]`). The
console reads no authored value for it. At the `.objectui-sha` pin
`89cad75d5570` (and unchanged at the current pin `ab1879721595`:
`MasterDetailForm.tsx` and `deriveMasterDetail.ts` are byte-identical
between the two), `MasterDetailDetailConfig` has no `sortField` member
(`plugin-form/src/MasterDetailForm.tsx:83`). The field the line grid
stamps with each line's position is the one `deriveDetail` derives from
the child object (`deriveMasterDetail.ts:540`), and it reaches the grid
as `sort_field` (`:874`). The spec still declared the key, so an
authored `sortField` went through `os validate` clean and was then
dropped.
This executes the direction triage confirmed (`5969870827`), the one
recorded on #21220's landing (`5937457620`) and mirrored on
objectstack-ai/objectui#11396 ③: a `retiredKey()` tombstone, one
ADR-0087 D2 conversion and one D3 entry, with no staged window because
the writer census is zero. There is no objectui edit here: the
objectui#11396 mirror follows once a published `@objectstack/spec`
carries this.
## The retirement kit
- **Tombstone** on the strict detail entry
(`packages/spec/src/ui/component.zod.ts`). The refusal is the
prescription, and the key's input type is the retired-key mark, so `tsc`
refuses it too. The prescription opens with the fully qualified key:
`object-master-detail-form` property `details[].sortField`. It ends with
the house `os migrate meta --from 17` sentence.
- **One declaration of the derived sort-field names.**
`INLINE_GRID_SORT_FIELDS` (the same six names in the same order as the
renderer's `SORT_FIELD_NAMES`) moves out of
`data/inline-grid-columns.ts` into `data/inline-grid-sort-fields.ts`, a
module reached only by relative import, like
`ui/action-target-aliases.ts`. Exported from `inline-grid-columns.ts`,
it would have reached the published API through the `data` barrel.
`inline-grid-columns.ts` imports it, and the tombstone's prescription
and `record:line_items`' answer print the list from it
(`INLINE_GRID_SORT_FIELD_LIST`). `check:api-surface` is unchanged, as
expected.
- **D2 conversion
`object-master-detail-form-detail-sort-field-removed`** (step 18,
`retiredFromLoadPath`, `retiredAfter: '17.6.0'`, order 60). It is a
lossless delete of `sortField` from each `properties.details[]` entry of
an `object-master-detail-form`, scoped by component type and by
position. Its fixture covers region, nested-card and slotted positions,
an open-namespace control with the same shape, and an entry that is not
an object.
- **`RETIRED_KEYS_BY_MAJOR[18]`** registers the nested key
`ui/ObjectMasterDetailFormProps:details.sortField` (an array member,
spelled without its `[]`). It has no `authorable-surface/` line, so
checks (b2) and (b3) resolve it against the emitted schema, and
`check:authorable-surface` is green on it.
- **D3 entry `object-master-detail-form-detail-sort-field-retired`**,
plus a `STEP18_RATIONALE` fragment at order 70.
- **Texts that became false, changed in this PR:**
- the entry docblock: "exactly the twelve keys" becomes eleven plus the
tombstone, re-read at the pin, and "the three it adds … and `sortField`"
now says the line-position field is derived;
- `record:line_items`' guidance for `sortField`, which used to say the
key "belongs to an `object-master-detail-form` detail entry" and now
says no block takes it;
- the generated reference page row
(`content/docs/references/ui/component.mdx`);
- the carriers test's every-key control (`FULL_DETAIL_ENTRY`, now 11
keys);
- two landed step-18 D3 entries.
`ui-object-master-detail-form-details-closed` listed `sortField?` among
the keys to write, and `ui-record-line-items-props-closed` sent
`sortField` to the detail entry. Both entries shipped in 17.6.0's
`registry.ts`. Step 18 is still open, so the 17 → 18 upgrade guide will
be generated from these texts. Reviewer, please confirm that this
correction belongs in this PR.
- **Changeset** `@objectstack/spec` `minor`, with a BREAKING banner, the
FROM → TO table and the ADR-0087 `registered` disposition.
## Premise checks (measured on origin/main 9a4182a, objectui at the
pin 89cad75d5570)
- Locations: the key was at `component.zod.ts:5566`, the docblock at
`:5539` and the `record:line_items` guidance at `:2201`–`:2202`, as the
claim said.
- Renderer: `MasterDetailForm.tsx:83` says "⛔ No `sortField` member".
The only `sortField` on that path is the derived one (`:372`, `:874`,
`:1066`, `:1081`; `deriveMasterDetail.ts:55` and `:540`). A fully
configured entry (FK plus every column typed) loads no child schema
(`:977`–`:979`), so it stamps no line position. The prescription says
this.
- Writer census, `git grep -n -w sortField` over `examples apps packages
skills content/docs`, excluding `packages/spec/**` and CHANGELOGs: 1
hit, the generated reference page. Same instrument, control `addLabel`:
6 hits, including the showcase project workspace's detail entry. At the
objectui pin, the only detail entries that write the key are probes
asserting that nothing reads it
(`masterDetailDetailsMembers-8071.test.tsx` rows 2c and `:295`/`:304`).
No objectui source assigns it through a spec type.
- Landing site: `packages/spec` is the producer of the declared key. No
other package was involved.
## Evidence
All on the final head `f765e8caaf` unless noted. The branch merged
`origin/main` three times: at `e367002e11` (which carries the sibling
element:text landing `36ad3210d4`), at `5b5e83f446` (which carries
#21622's landing `3f1bc816a2`), and at `15fe567c9c` (which carries
#21625's pin bump `1cbe165bfc` and #21642's `15fe567c9c`). Readings
marked `69f42d393d` predate the second merge.
- `@objectstack/spec` local project at `f765e8caaf`: 609 files and 18059
tests passed (1 todo), with no skip.
- `@objectstack/spec` repo project at `f765e8caaf`: the three
merge-shape and retirement files (`step18-rationale-merge`,
`conversions-major18-merge` and
`master-detail-detail-sort-field-retirement`) passed 41 tests. The wider
repo-project run is a `69f42d393d` reading: 52 of 53 files passed. NOT
MEASURED: `scripts/build-schemas-check-mode.test.ts`, which alone
exceeds the 590 s foreground cap. `check:authorable-surface`, the gate
whose mode it tests, is green at `f765e8caaf`.
- `pnpm --filter @objectstack/spec typecheck`: exit 0, including
`check:test-typecheck`. The new pin's `@ts-expect-error` is compiled
there, so an unused directive would be red.
- Consumer packages that author an `object-master-detail-form`
`details[]` entry:
- `@objectstack/lint` at `f765e8caaf`:
`validate-component-props.test.ts` passed 51 tests (including the new
advisory pin). At `69f42d393d`, all 119 files and 5621 tests passed, and
its typecheck passed;
- `@objectstack/example-showcase` at `69f42d393d`: its typecheck passed,
and 32 files and 399 tests passed.
- `pnpm --filter @objectstack/spec check:generated`: all 15 artifacts
are up to date.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 114 commands at `f765e8caaf` (the
same set as at `a1b0552acd`), and all 114 exited 0 on the first attempt
after a full build. `--ran` reconciled them as 114 derived, 114 run, 0
NOT-MEASURED, 0 UNRUN. At `a1b0552acd`, the round report that first
quoted the same reading had stale provenance; the seat's note
`5974199078` on the card corrects it.
- ESLint, as a proven narrowing rather than the repo-wide `pnpm lint`:
- Population: `eslint.config.mjs` lints
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`.
- `--no-inline-config --format json` over the 12 changed lintable files
at the final head `f765e8caaf` reported 12 files, 0 errors and 0
warnings (no ignored-file warnings, so all 12 are in the population). An
earlier run, before the two D3 entry text edits, read 10 files with the
same result.
- Invariance: the config never enables type-aware linting (no
`parserOptions.project` and no `projectService`), and the diff touches
neither the config nor its baselines, so no verdict on an untouched file
can move.
- Ablations: one-shot runs through `scripts/ablation-replace.mjs`,
wrapped in a trap. Each restore is proven by blob == HEAD and an empty
`git diff HEAD`.
- The tombstone reverted to `z.string().optional()`: 5 of the pin's
tombstone rows went red (refusal, list, props-lint row,
tombstone-not-strict-arm, tsc channel), and 15 stayed green.
- `sortField: 'position'` planted on the showcase workspace's detail
entry: the tree-scoped absence walk went red, naming
`examples/app-showcase/src/ui/pages/project-workspace.page.ts authors
sortField: 'position'`.
## New pins
-
`packages/spec/src/ui/master-detail-detail-sort-field-retirement.test.ts`,
in the repo project because it walks the tree. It covers:
- the tombstone: issue `code` `invalid_type`, path
`details.0.sortField`, the prescription, and the derived names drawn
from the one declaration;
- that the props-lint row is the same schema;
- the tombstone against the strict-arm control;
- the walked shape keeping the key;
- the tsc channel;
- that the page parse never refuses;
- `record:line_items`' answer;
- the D2 strip: stored row, built artifact in every position,
by-reference control, type and position scoping, idempotence, retired
from the load path;
- the ledger row and the D3 entry;
- a tree-scoped absence walk over the radius `@objectstack/spec` already
declares. `check:cross-package-test-inputs` is green, and the test is
listed in `vitest.repo-tests.json`.
- `packages/lint/src/validate-component-props.test.ts`: a detail-entry
`sortField` is a `component-props-invalid` warning at
`properties.details.1.sortField`, carrying the prescription.
## Acceptance notes
- The D3 entry's `replacement` spells out the six derived names
literally. The generator copies entry values into `registry.ts` and
keeps no imports, so it cannot read the declaration. The pin asserts
that every member of `INLINE_GRID_SORT_FIELDS` appears in it.
- Deployed metadata is NOT MEASURED. Stored pages are converted by the
D2 strip when they are read, and the props lint reports live sources.
- Merge order with this seat's siblings: the element:text variant
retirement (`36ad3210d4`) landed first at conversion order 59 and
rationale order 68. #21622 (`3f1bc816a2`) landed next with rationale
order 69. This PR takes 60 and 70. All three merges went through
`scripts/pm/os-regen-merge.sh`. Each time, the os-regen driver kept this
branch's copy of `component.mdx` and dropped main's rows: the variant
row, then #21622's `object-metric` rows, then #21625's `object-grid`
`keyboardNavigation` row. Each time, the page was regenerated from the
merged source in its own commit (the last is `f765e8caaf`). Measured by
comparing change lines, the head's delta against `main` is this PR's own
delta at `a1b0552acd`, file by file; and the head's delta against
`a1b0552acd` is `main`'s own delta. #21625 did not touch the detail
entry or its docblock.
- CI at `f765e8caaf`: complete and green, 33 success and 2 skipped
(`Console Pin Gate` and the opt-in packed-tarball smoke, by their own
filters). `Lint & Repo Gates`, red at `a1b0552acd` only because GitHub's
issues API answered HTTP 503 in `check-issue-citations`, is success on
this head.
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(spec)!: an `object-master-detail-form` detail entry's `sortField` is retired — the console derives the line-position field from the child object and reads no authored value (#21589)
**BREAKING** — an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for accept-set narrowings.
12
+
13
+
`ComponentPropsMap['object-master-detail-form'].details[].sortField` named the child field the line grid stamps with each line's position on drag-reorder. The console stopped reading it: the field it stamps is derived from the child object, and the pinned console crossed that change while the spec still declared the key. So an authored `sortField` went through `os validate` clean and was dropped, and a drag-reorder stamped the derived field, or none (ADR-0049 enforce-or-remove).
14
+
15
+
### FROM → TO
16
+
17
+
| before | what to write instead |
18
+
| --- | --- |
19
+
|`details: [{ childObject: 'crm_invoice_line', sortField: 'line_no' }]`| delete `sortField`. The grid stamps the child object's first field named `position`, `sort_order`, `sequence`, `line_no`, `line_number` or `sort`. |
20
+
|`sortField` naming a field outside that list | give the child object one of those fields; the line order is kept there. |
21
+
| an entry that names `relationshipField` and at least one column and gives every column a `type`| unchanged: the renderer keeps that entry exactly as authored, loads no child schema for it, and stamps no line position, before and after the upgrade alike. |
22
+
23
+
**The one-line fix: delete `sortField` from every `object-master-detail-form` detail entry.**`os migrate meta --from 17` lists the mechanical edits for existing sources; apply them by hand.
24
+
25
+
**What an author now sees.** Writing the key fails `tsc` (its input type is the retired-key mark), and `os validate`, `os build` and `os lint` report it as a `component-props-invalid` warning carrying the prescription at `properties.details.N.sortField`. A page that carries it still saves and loads: a page component's `properties` is not parsed on the metadata save or load path.
26
+
27
+
### The retirement kit
28
+
29
+
-**Tombstone.**`sortField` is a `retiredKey()` on the strict detail entry. Its prescription prints the derived field names from their one declaration, a module reached by relative import only (`data/inline-grid-sort-fields.ts`), which the derived inline-grid columns read too.
30
+
-**D2 conversion `object-master-detail-form-detail-sort-field-removed`** (step 18, retired from the load path): a lossless delete of `sortField` from every `properties.details[]` entry of an `object-master-detail-form`, scoped by component type and by position. Stored `sys_metadata` pages and built artifacts replay it, one notice per entry.
31
+
-**D3 entry `object-master-detail-form-detail-sort-field-retired`** carries the judgment the delete cannot make: whether the child object declares the field the line order is kept in.
32
+
-**`RETIRED_KEYS_BY_MAJOR[18]`** registers the nested key `ui/ObjectMasterDetailFormProps:details.sortField`.
33
+
-**`record:line_items`' answer to `sortField`** no longer sends the author to the detail entry: no block takes an authored `sortField` any more.
34
+
-**No deprecation window**: the writer census is zero.
35
+
36
+
**Measured producers: none.** On origin/main 9a4182a752, no `object-master-detail-form` detail entry in `examples/`, `apps/`, `packages/`, `skills/` or `content/docs/` writes `sortField`, against the sibling detail-entry key `addLabel` on the showcase project workspace's entry as the control, through the same instrument. At the objectui pin `89cad75d5570` the only detail entries that write it are probes asserting that nothing reads it. Deployed metadata NOT MEASURED.
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1084,7 +1084,7 @@ Sort field and direction pair
1084
1084
|**formFields**|`string[]`| optional | Child field names for the per-row expand form. When omitted they are derived from the child object's fields — except on an entry that names both `relationshipField` and at least one column, which is kept as authored: nothing is derived, and the per-row form is offered only when `inlineMode` is 'form', where it draws the child object's full field list |
1085
1085
|**inlineMode**|`Enum<'grid' \| 'form'>`| optional | Inline-edit form factor: 'grid' = editable cells; 'form' = read-only list + per-row full form. When omitted it is resolved from the relationship field's `inlineEdit`, else from the child object's shape — except on an entry that names both `relationshipField` and at least one column, which is kept as authored: nothing is resolved, the collection renders as a grid, and the per-row form is offered only when `formFields` lists more fields than `columns`|
1086
1086
|**amountField**|`string`| optional | Numeric child column summed for the running total and the `totalField` rollup. When omitted it is picked from the grid's number and currency columns: a computed one, else one named `amount`, `total`, `subtotal`, `line_total`, `line_amount` or `net_amount`, else the last currency column, else the last numeric one — except on an entry that names `relationshipField` and at least one column and gives every column a `type`. The renderer keeps that entry exactly as authored, so nothing is picked. With no `amountField` authored or picked, the sums read a child column named `amount`, and the grid shows a running total only when `totalField` is set |
1087
-
|**sortField**|`string`| optional |Child field holding the line sort position, stamped on drag-reorder. When omitted it is the child object's first field named `position`, `sort_order`, `sequence`, `line_no`, `line_number`or`sort`, if it has one — except on an entry that names `relationshipField` and at least one column and gives every column a `type`. The renderer keeps that entry exactly as authored: nothing is derived, the grid stamps no line position, and a drag-reorder is not saved|
1087
+
|**sortField**|`never`| optional |[REMOVED]`object-master-detail-form` property `details[].sortField` was removed in @objectstack/spec 17 (ADR-0087 D2) — the console reads no authored value: the field the line grid stamps with each line's position on drag-reorder is derived from the child object, so an authored `sortField` was accepted and dropped. Delete the key. For a drag-reorder to be saved, give the child object a field named `position` / `sort_order` / `sequence` / `line_no` / `line_number`/`sort`: the renderer stamps the child's first field with one of those names — except on an entry that names `relationshipField` and at least one column and gives every column a `type`, which the renderer keeps exactly as authored and stamps no line position on. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand.|
1088
1088
|**totalField**|`string`| optional | Parent field to receive the rolled-up sum |
1089
1089
|**title**|`string`| optional | Section title |
1090
1090
|**minRows**|`number`| optional | Minimum number of rows |
0 commit comments