Skip to content

feat(spec)!: retire the object-master-detail-form detail entry sortField — the console derives the line-position field from the child object (#21589) - #21632

Merged
objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-21589-detail-sortfield-retire
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-21589-detail-sortfield-retire

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

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

claude added 9 commits October 3, 2026 16:33
…ield

The console reads no authored value: the line grid stamps the field it
derives from the child object. retiredKey() tombstone, D2 strip conversion,
nested RETIRED_KEYS_BY_MAJOR row and D3 entry; the derived sort-field names
move to one relative-import-only declaration the prescription prints.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
… and the lint pin

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
… the repo project

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
The os-regen driver kept the branch's copy of component.mdx in the merge,
dropping the element:text variant row main landed; regenerated from the
merged source, the page carries both rows.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
…ariant landing

The element:text variant retirement landed at conversion order 59 and
rationale order 68; this retirement moves to 60 and 69.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
…ntry key

The detail-entry closure's replacement listed `sortField?` among the keys to
write, and the record:line_items closure sent `sortField` to the detail entry;
both are false once the key is a tombstone there.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 27 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectMasterDetailFormProps__details.sortField.ts, packages/spec/vitest.repo-tests.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via crm_quote (literal, a string literal in fixture))
  • content/docs/getting-started/examples.mdx (via crm_order (literal, a string literal in fixture))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via crm_quote (literal, a string literal in fixture))
  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object objectMasterDetailFormDetailSortFieldRemoved), retiredFromLoadPath (symbol, a field of const object objectMasterDetailFormDetailSortFieldRemoved))
  • content/docs/releases/v17/17-6.mdx (via RETIRED_KEYS_BY_MAJOR (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectMasterDetailFormProps__details.sortField.ts, packages/spec/vitest.repo-tests.json) — pages documenting those are invisible to this run
  • 9 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 15fe567c9c74e088684944094aa686b9bd3b386c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4f65d88f0b7b23d18641301a8fd5a59a62d45734 — the merge of head f765e8caaf8854f8677350dc153dbbbb3a856945 into base 15fe567c9c74e088684944094aa686b9bd3b386c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4f65d88f0b7b23d18641301a8fd5a59a62d45734 && git checkout 4f65d88f0b7b23d18641301a8fd5a59a62d45734
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 15fe567c9c74e088684944094aa686b9bd3b386c f765e8caaf8854f8677350dc153dbbbb3a856945 && git checkout -B drift-repro 15fe567c9c74e088684944094aa686b9bd3b386c && git merge --no-ff f765e8caaf8854f8677350dc153dbbbb3a856945

node scripts/docs-audit/affected-docs.mjs --json 15fe567c9c74e088684944094aa686b9bd3b386c

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 15fe567c9c74e088684944094aa686b9bd3b386c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 3 commits October 3, 2026 21:30
…gen step 3 hand-off)

The os-regen driver kept this branch's side of
content/docs/references/ui/component.mdx in the merge commit and dropped
main's object-metric rows from #21622. Step 2 restored main's side; this
commit regenerates the page from the merged source with gen:schema and
gen:docs. Result: main's side plus this branch's one `sortField` row,
and the delta against the branch side is exactly #21622's own delta on
the page.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
…s order 70

#21622 (#21464 stage 4) landed on main as 3f1bc81 with its step-18
rationale fragment `ui-object-metric-aggregate-trend-typed` at order 69,
the order this branch's fragment also held. Re-read on the merged tree's
main side (5b5e83f) the highest STEP18_RATIONALE order is 69, so this
fragment takes the next free one, 70, and now renders after #21622's.
The D2 conversion keeps order 60: the highest MAJOR_18_CONVERSIONS order
on main is still 59 (`elementTextVariantHeadingLevels`).

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Lint & Repo Gates on a1b0552acd (job 111298043709) is red at one step, "Issue citations this change adds resolve on the board". It exited 3: check-issue-citations: PREREQUISITE NOT MET — the board was not read. GET issues?per_page=1 -> HTTP 503.

domain:spec seat 1 · os-project-manager · session session_01T9u38rswFp5Rw8DswRUReJ · 2026-10-03T22:52Z

claude added 2 commits October 3, 2026 23:34
…gen step 3 hand-off)

The os-regen driver kept this branch's side of
content/docs/references/ui/component.mdx in merge commit 5f5b809 and
dropped main's object-grid keyboardNavigation row (1cbe165). Step 2
restored main's side; gen:schema and gen:docs on the merged tree (HEAD
the merge commit, no MERGE_HEAD) produce this page. Its delta against
origin/main 15fe567 is exactly this branch's one sortField row.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants