Skip to content

feat(spec): retire object-grid's legacy defaultSort fallback (#11805, ADR-0049) - #12256

Merged
os-litant merged 4 commits into
mainfrom
claude/issue-11805-retire-object-grid-default-sort
Aug 25, 2026
Merged

feat(spec): retire object-grid's legacy defaultSort fallback (#11805, ADR-0049)#12256
os-litant merged 4 commits into
mainfrom
claude/issue-11805-retire-object-grid-default-sort

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#11805

Producer half of objectui#5861 (ADR-0049 enforce-or-remove), per the maintainer ruling of 2026-08-25 (decision-inbox batch 4): 「#11805 退役 defaultSort,不需要major」.

What

ObjectGridPropsSchema.defaultSort — the legacy single-sort fallback ({ field, order }, read only when sort is absent) — is retired from the authoring contract. Authoring it is now a tsc error (never input) and a parse error carrying the wrap-and-rename prescription (sort: [{ field, order }]). Zero authored occurrences in either repo's corpora (the card's measurement, re-run at dispatch across examples/, apps/, packages/qa/, spec fixtures, skills/, content/docs — the only doc hit is the generated reference row this PR regenerates).

Read points measured at the .objectui-sha pin (190fbd01d): plugin-grid/src/ObjectGrid.tsx:1244-1246 (the $orderby fetch fallback) and :2847 (header arrows, which wrap it [schema.defaultSort] — the exact array shape sort carries). objectui's mirror schema is parity-test-only and parses nothing at runtime, so only this strictObject can refuse the key; objectui#5861 retires the reads as the consumer half after a released spec version reaches the pin.

The retirement kit

  • TombstonedefaultSort: retiredKey('…') in ObjectGridPropsSchema (packages/spec/src/ui/component.zod.ts); the authorable-surface line becomes ui/ObjectGridProps:defaultSort [RETIRED].
  • ADR-0087 registrationentries/retired-keys/18.ui__ObjectGridProps__defaultSort.ts (RETIRED_KEYS_BY_MAJOR[18], via gen:migration-registry); D2 conversion object-grid-default-sort-removed (conversions/registry.ts, protocol 18): wrap-and-rename to sort: [pair] when sort is absent, pure lossless strip when sort is present (the renderer's own precedence made it unread then); wired into the step-18 chain (conversionIds + rationale).
  • Pin testscomponent.test.ts: refusal pin asserting the prescription, no-materialize pin, surviving-sort pin.
  • Generated baselinesauthorable-surface/ui.json ([RETIRED], written by gen:schema during build; build gate (b) passed against the registered exact key) and content/docs/references/ui/component.mdx (gen:docs, the one artifact check:generated proved stale). spec-changes.json / upgrade guide are green unchanged — protocol-18 entries project at the next major boundary, same as every sibling 18-registered retirement.
  • Changeset@objectstack/spec: minor per the ruling's explicit 「不需要major」 (launch-window convention: accept-set narrowings ride minor), body carries BREAKING + FROM→TO + one-line fix + <!-- adr-0087: registered object-grid-default-sort-removed -->.
  • Liveness ledger — measured no-op: liveness/page.json classifies PageSchema top-level props only (one drill level, per its own _note); component-props keys have no row to move, and check:liveness is green.

Route note (deviation from the card's literal wording, declared)

The card (and the playbook's strict-schema row) spells the route "strict deletion + guidance-map entry". The authorable-surface ratchet forbids that spelling here: gate (a) in packages/spec/scripts/build-schemas.ts is fatal on any key that vanishes from an emitted def ("To retire a key, tombstone it instead of deleting it"), and gate (c) ratchets baseline-line deletions against the merge base for two majors. The data/Metric:filters guidance-route precedent predates its def's arrival in the surface baseline (data/Metric:* first entered authorable-surface/data.json at dd33bf9, already without filters), so it never met these gates; ui/ObjectGridProps:defaultSort IS in the merge-base baseline, so the only gate-green shape is the retiredKey() tombstone — the exact current shape of every in-repo component-props retirement (#6946 ×3, FormView.defaultSort, #9198, #9220), which the dispatch named as shape authority. Author-visible behaviour is identical to the ruled outcome: authored defaultSort flips from accepted to a loud rejection carrying the fix; changeset stays non-major. No semantic entry ships — the migration story is the D2 conversion (mechanical, and required so applyConversionsToStoredItem heals stored rows the strict parse now refuses), matching the FormView/#6946 wiring.

Verification (head 9b3dd89, post-merge of origin/main incl. #12191)

  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2Test Files 426 passed (426) / Tests 11355 passed (11355) (pre-merge head 38577eb); post-merge union re-run on 9b3dd89: component.test.ts + conversions + migrations + alias-integrity + migrate-sentence pins — Test Files 8 passed (8) / Tests 575 passed (575). Pin receipts (verbose): ✓ object-grid defaultSort is retired (#11805) > rejects the retired defaultSort with the wrap-and-rename prescription / does not materialize / keeps sort.
  • pnpm --filter @objectstack/spec typecheck — exit 0 (tsc + scripts + test layer).
  • pnpm --filter @objectstack/spec check:generated @ 9b3dd89 — "✓ All 14 generated artifacts are up to date."
  • tsc reverse verification against rebuilt dist/: probe authoring defaultSort reds (error TS2322: Type '{ field: string; order: string; }' is not assignable to type 'undefined'), sort control compiles exit 0.
  • node scripts/check-adr-0087-registration.mjs @ 9b3dd89 — ".changeset/object-grid-default-sort-retired.md [BREAKING] registered object-grid-default-sort-removed"; check-changeset-no-major — "✓ This diff introduces no major bump."; check-empty-changeset — pass.
  • Gate battery (each exit captured before any pipe): liveness · empty-state · strictness-ledger · variant-docs · spec-parsed-alias · slot-lookup · test-source-alias · type-source-resolution · query-options-erasure · merge-driver · changeset-gate-self-tests · objectui-changeset · published-files · doc family (anchors/authoring/audit-scope/redirects/frontmatter/route-spelling/section-name/landing-index/quick-reference-counts/role-word/react-page-adapter/security-posture/formula-expressions) · cross-package-test-inputs · ci-filter-parity · plugin-teardown-shape · engine-double-contract · where-matcher · type-check-coverage · docs-audit ×2 · release-rehearsal-clone --self-test · check:skill-examples (after building client-react) · check-nul-bytes — all PASS.
  • Consumer sweep (downstream direction, ...@objectstack/spec): the dispatch-named consumer @objectstack/lint (metadata-authoring-lint, consumes ComponentPropsMap) — 81 files / 2294 tests + typecheck, green. objectui consumes via the .objectui-sha pin; no cross-repo change (ui#5861 follows on pin bump).
  • Declared narrowings: check-dev-prereqs fails only on the fresh worktree's unbuilt workspace (34/67 packages, an environment precondition CI's lint.yml discharges by building first); check:type-check-debt --re-measure and the full pnpm lint sweep are CI-owned farm runs; CLI migrate-meta e2e not run locally (conversion idempotent by construction — the 'defaultSort' in properties guard; CI runs it).
  • Sibling discipline (fix(spec): minLength is a positive integer, authorable only on bounded-string field types; register the deferred #11566 maxLength ADR-0087 entry #12191 landed mid-flight): merged via scripts/pm/os-regen-merge.sh (merge committed before regen), regenerated, then asserted by quoted-exact-name grep that the sibling's field-max/min-length-malformed-or-misplaced-refused entries, object.editMode, minLength bodies, and this PR's entries all survive.

skills/** untouched; no governed surface in the diff; PR stays draft for the needs:contract-review chain (条款②).

Generated by Claude Code


Generated by Claude Code

… ADR-0049)
Tombstone ObjectGridPropsSchema.defaultSort with retiredKey() — the legacy
single-sort second spelling of sort, read only when sort was absent (measured
at the .objectui-sha pin 190fbd01d, plugin-grid/src/ObjectGrid.tsx:1244-1246
and :2847). Register ui/ObjectGridProps:defaultSort in RETIRED_KEYS_BY_MAJOR[18],
add the D2 conversion object-grid-default-sort-removed (wrap-and-rename to
sort: [pair] when sort is absent; pure strip when sort is present) wired into
the step-18 chain, regenerate the authorable surface ([RETIRED] mark) and the
ui/component reference, and pin the refusal + no-materialize + surviving-sort
behaviour in component.test.ts. Changeset ships minor per the maintainer's
#11805 ruling (launch-window convention).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
)
gen:migration-registry (byte-identical — main's generated regions already
carried the #12191 semantic entries), rebuild restoring the
ui/ObjectGridProps:defaultSort [RETIRED] surface mark, gen:docs restoring the
retired reference row. Sibling entries and implementation bodies asserted
present (field-max/min-length semantic entries, object editMode, minLength).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

27 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 107bb4ba4b96bb74913e19b46f7756dd30029d4d.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ObjectGridProps__defaultSort.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 34 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 177: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 107 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.

Coarse fallback — 126 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 107bb4ba4b96bb74913e19b46f7756dd30029d4dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 785f6526b02617e015a3cc0af31755712d4405a1 — the merge of head 9b3dd895fcbc52332d02804a84c8f3981a38a834 into base 107bb4ba4b96bb74913e19b46f7756dd30029d4d, 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 785f6526b02617e015a3cc0af31755712d4405a1 && git checkout 785f6526b02617e015a3cc0af31755712d4405a1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 107bb4ba4b96bb74913e19b46f7756dd30029d4d 9b3dd895fcbc52332d02804a84c8f3981a38a834 && git checkout -B drift-repro 107bb4ba4b96bb74913e19b46f7756dd30029d4d && git merge --no-ff 9b3dd895fcbc52332d02804a84c8f3981a38a834
node scripts/docs-audit/affected-docs.mjs --json 107bb4ba4b96bb74913e19b46f7756dd30029d4d

⚠️ 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 107bb4ba4b96bb74913e19b46f7756dd30029d4d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[spec] ADR-0049 enforce-or-remove: retire ObjectGridProps.defaultSort (the producer half of objectui#5861)

2 participants

@os-litant@claude