Repository navigation
Follow-up (the "C half" of the 2026-08-22 ruling): ADR-0049 enforce-or-remove retirement of table.defaultSort #5861
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Aug 24, 2026 Triage (daily round, session
session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24): maintainer-directed follow-up (2026-08-22 ruling on #4869, C half quoted in body) →domain:ui+pm:queue, Task: ADR-0049 enforce-or-remove retirement oftable.defaultSort. Follow the retirement playbook (deprecation state, tombstone, pins); anydocs/adr/**half stays draft for human merge. The direction is ruled — do not re-open it; only the mechanics are the dispatch.
Generated by Claude Code
Claim: PM loop round 38 (refill slot)
Session:session_01CSoz9uGhaaSgiq3hshtN7L
Branch:claude/issue-5861-retire-table-defaultsort
Worktree:objectui-issue-5861
Domain:domain:ui
Container & model:L,mode:subagent,model: opus
Clause-②: yes — a published key is retired and, per item 1 below, a reader is expected to start refusing it. Declared; Opus under this seat's recorded Fable-quota exemption.File surface:
packages/types/src/objectql.ts(theObjectGridSchema.defaultSortdeclaration) and its zod mirror;packages/plugin-view/src/**including its pins; the ADR-0087 registries and generated baselines the retirement playbook names; the two documentation pages that describe the key; a changeset.⛔ Two hard fences on that surface:
- ⛔
packages/typesis shared with finding(types): designer-surfaceDashboardConfig.aria/DashboardConfigSchema.ariadeclare a{ label, description }aria object nothing reads — and the panel they document does not consume them #5852, in flight right now (retiringDashboardConfig.aria+ its zod mirror). Stay inobjectql.tsand its mirror; ⛔ if your edit needs the dashboard-config declarations or the same barrel lines, stop and report.packages/typesalso had feat(types,plugin-gantt): declare the ten gantt keys ObjectGantt reads through a cast #6053, fix(types): widen seven zod mirrors the renderer already implements (#5927 group A) #6032, ComponentMeta: re-export the one declaration, then deprecate PluginComponentMeta at its final meaning #6068 land today and refactor(types): retire the orphaned ObjectTrigger and ObjectRelationship exports #6085 is enqueued (field-types.ts+index.ts) — branch from a base containing what has landed. - ⛔ Anything under
docs/adr/**is OUT of this card. That is a governed surface: draft PR only, human merge is the review record, ⛔ never enqueued — so mixing it into a code PR makes the whole PR unlandable by the PM. If the retirement genuinely requires an ADR edit, ✅ say so in the report and leave it undone; it becomes its own draft-only PR.
⚠️ Serial, adjacent semantics: #6088 (#5795) is enqueued and normalises related-list sort through@object-ui/core'sconvertSortToQueryParams— the same sink this card's key feeds. #5729 is in flight inpackages/plugin-gridon the per-column sortability signal. ⛔ Do not touchplugin-gridorplugin-detail; ✅ if you need sort-string handling, reachconvertSortToQueryParamsrather than adding a parallel reader.裁决 — ruled, and ⛔ not re-openable
Maintainer ruling 2026-08-22 on #4869 (decision-inbox digest, verbatim 「接受所有」): Option A now, plus "the follow-up card for the C half — an ADR-0049 enforce-or-remove retirement of
table.defaultSortas its own card, not folded into this one." Option A has landed.Triage (2026-08-24 01:30Z), verbatim:
maintainer-directed follow-up … →
domain:ui+pm:queue, Task: ADR-0049 enforce-or-remove retirement oftable.defaultSort. Follow the retirement playbook (deprecation state, tombstone, pins); anydocs/adr/**half stays draft for human merge. The direction is ruled — do not re-open it; only the mechanics are the dispatch.⛔ So: do not re-argue whether the key should go. ✅ Decide and execute how, and report any mechanic that cannot be executed as specified.
The three things the card says a removal must settle
- ⛔ Refuse loudly — do not merely stop reading. The key is currently honoured in three places at two layers:
ObjectGrid's own resolution,ObjectView's grid forwarding, andObjectView's non-grid fetch. Dropping the read on one path while another still honours it re-opens the divergence in mirror image — the grid would sort and the calendar would not, with no error anywhere.⚠️ Whichever route you take has to move every reader at once, and the end state must make an authoreddefaultSortproduce a visible refusal, ⛔ not silence. - The registries. ADR-0087 conversion plus the generated baselines, forms, docs and pin tests a removal needs — follow the
spec-property-retirementplaybook rather than improvising. ✅ Name each registry you touched and each you deliberately did not. ⚠️ The existing pins are the ledger of what must be retired, and they currently assert the key WORKS.packages/plugin-view/src/__tests__/ObjectView.canonicalTableKeys.test.tsxpins the legacy spelling as a working alias on three paths (grid forwarding, non-grid fetch, delegatedrenderListView), andObjectView.sortSink.test.tsxpins the lowered value. ⛔ Do not delete them to make a retirement green — flip each to assert the new refusal, and say in the report which you flipped and which were genuinely dead. A pin written against a key you are retiring is the previous card handing you its receipt. (This exact move was executed correctly on finding(app-shell):view-column-io's colLabel/colFieldName still read the retiredaccessorKey/headeraliases, so the column LIST re-shows an identity the column inspector just stopped showing #5725 today, on a pin that was even documented to flip.)
PM mechanism assumptions — measure, do not inherit
- ⛔ Re-measure "zero authored occurrences" on YOUR merge-base, with a counter-probe. The card measured it on
origin/main@1d9100eeb; ~30 PRs have landed since. Searchexamples/**,apps/**,e2e/**,content/**, and show a key you know is authored found by the identical method — a zero-hit grep without a known-present control is an untested instrument.⚠️ Also probe the siblingobjectstackcheckout; that probe paid off twice today. ⚠️ Expect two classes of surviving hit that are NOT authoring, and do not confuse them: the registry/spec-parity ledger inapps/console/src/__tests__/registry-inputs-spec-parity.test.ts, and prose incontent/docs/plugins/plugin-view.mdx+content/docs/api/schema-reference.mddescribing it as the deprecated half of a pair. ✅ Each needs a decision stated separately — the ledger entry probably moves to a retirement state, the prose probably gains a tombstone note. ⛔ Neither is "an authored occurrence".⚠️ The key is already live-for-reading but not-for-writing — [Decision] spec 17.0.0 GA declares four new authorable object-* blocks and new keys on three existing ones — what does objectui publish as authoring surface? #4648 (maintainer, 2026-08-16) carvedobject-grid.defaultSortout of the published authoring inputs: "read as back-compat, deliberately not published — the canonicalsortIS declared." ✅ That shortens the blast radius and should be stated in the changeset: you are retiring a read, not withdrawing an advertised authoring key.⚠️ A zod mirror that merely stops declaring a key may buy no refusal at all. Measured on ComponentMeta: re-export the one declaration, then deprecate PluginComponentMeta at its final meaning #6068 today: a plainz.objectwith no.strict()strips unknown keys rather than rejecting them. ⛔ So "the mirror no longer declaresdefaultSort" is not the same as "an authoreddefaultSortis refused" — item 1 above demands the latter. Measure which your change actually produces and say so.- Out of scope, explicitly: ⛔ this card is not about
ObjectViewProps.views[].sortand itsdirection/orderspelling mismatch — that isObjectViewProps.views[].sortdeclaresdirection, but every sort consumer readsorder— a host-supplieddescview silently sorts ascending #5293, which stays open and untouched. (Its residue is filed astoSortItems— a published export — still tolerantly reads the retireddirectionsort spelling (s.order || s.direction), dormant because nothing in-tree calls it #6011.)
Verification
Predict the direction before running.
⚠️ Commit your own edit before you ablate — measured on #5859 today: atraprestore usinggit checkout --reverted the dev's still-uncommitted edit along with the probe's mutation, andgit checkout --reported success both times; the half-change was caught only by an independentgit diff --statre-read.⚠️ Do any downstream counter-probe at the right layer: downstream resolves@object-ui/typesthroughexportstodist/, ⛔ notsrc/— a source-only mutation leaves the sweep green and reads as "nothing consumes it". Rebuild before reading and re-grepdist/.The retirement's positive evidence is a refusal that fires, on every one of the three reader paths, each with its own leg. Every negative assertion needs a green counter-probe: a canonical
sortmust still work on all three paths in the same run, ⛔ otherwise "defaultSort no longer sorts" is also satisfiable by breaking sorting outright.Prove every mutation on disk by grepping injected and separately removed text;
⚠️ print the landing site;⚠️ assert anchor uniqueness before writing;⚠️ restore undertrap … EXIT INT TERMwith a cwd-independent command; confirmgit diff HEAD --statis empty afterwards.Gates by name, with exit codes:
pnpm --filter @object-ui/types type-checkandpnpm --filter @object-ui/plugin-view type-check(⚠️ hyphenated; echo the script name; distinguish the unbuilt-closure red —Cannot find module @object-ui/...— from the missing-node_modulesred), root-form vitest over each package touched quoting per-test names, the downstream prefix-form sweep with the contract direction demonstrated rather than asserted (⚠️ known gotcha, not a finding:apps/siteandpackages/componentsfail until@object-ui/react-runtimeis built separately), eslint with merge-base delta (⚠️ plaineslint .; ⛔ no--no-inline-config). Runnode scripts/check-changeset-presence.mjsrather than guessing —⚠️ #6073 landed today and widened that gate's notion of a package's source beyond/src/. Changesetminorfor a public-type break; ⛔ nevermajor.⚠️ Watch for the zero-match pnpm trap (measured today): a filter that matches nothing prints "No projects matched the filters" and exits 0, reading exactly like a pass.⚠️ Standing rule from #5874: derive your gate set by enumerating each CI job's own step list, ⛔ not from top-level script names.Deliverable
Draft PR, body with
Fixes #5861. ⛔ Do not mark ready, ⛔ do not enqueue, ⛔ no auto-merge — the PM lands it. Post the terminal report here (plain-text first-line markeros-dev-report; ⛔ avoid short angle-bracket placeholders in prose — this repo's sanitizer eats them, confirmed twice today) and return it. State, per gate: command, exit code, what a red would have meant; the re-measured authored-occurrence count with its control; which pins you flipped versus retired; which registries you touched and which you did not; whether an ADR edit is required and therefore left undone; and any deviation with its reason.
Generated by Claude Code
- ⛔
os-dev-report
{ "issue": 5861, "status": "needs_decision", "branch": "claude/issue-5861-retire-table-defaultsort", "pr": null, "premise_still_valid": true, "summary": "The card's premises hold — re-measured zero authored occurrences, and the ruled direction (the key should go) is untouched and not re-argued. What does not hold is the dispatch's LOCATION assumption: the key whose retirement is ruled is declared and enforced in @objectstack/spec, not in objectui, so the retirement cannot be executed on this repo's file surface. ObjectGridPropsSchema (objectstack packages/spec/src/ui/component.zod.ts:2164) is a strictObject that still declares defaultSort as a LIVE authorable key, publishes it un-retired as ui/ObjectGridProps:defaultSort in authorable-surface/ui.json:734, and ACCEPTS an authored value today (measured). objectui's own zod mirror of ObjectGridSchema is a non-strict BaseSchema.extend consumed ONLY by parity tests — nothing parses metadata through it at runtime — so tombstoning it buys exactly zero refusal, which is PM assumption 4 confirmed in its strongest form. Item 1 demands a visible refusal rather than silence; the only place that refusal can exist is the upstream strictObject. Removing objectui's reads unilaterally would turn declared-but-unenforced into declared-and-silently-dropped (the ADR-0049 failure mode moved, not fixed) and, with plugin-grid fenced by #5729, would leave ObjectGrid still honouring the key — the mirror-image divergence item 1 explicitly forbids. objectui already states this ordering itself: the comment governing the ten ruled object-grid carve-outs in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts says they 'retire only if @objectstack/spec retires the keys upstream'. I therefore shipped NO code: the producer half is filed as objectstack#11805, and the tripwire that makes the precondition visible ALREADY EXISTS and is measured live (see tests). Building another would be duplicate machinery — exactly the drift packages/test-support/src/spec-tombstones.ts was written to end.", "tests": "Baseline measured on merge-base 0c282d979 (worktree clean throughout; git diff HEAD --stat empty, git status --short empty, verified after every probe). All heavy commands ran through scripts/pm/os-verify-lock.sh. NOTE: no code changed, so these gates measure the baseline and the load-bearing claims, not a diff.\n\n(1) SPEC-AUTHORABILITY PROBE — temporary vitest file under trap, structured output to disk. Result: object-grid listed=37, authorable=37, tombstoned=[]; defaultSort LISTED=true AUTHORABLE=true TOMBSTONED=false; tombstoneEvidence={listed:true, typedNever:false, describedRemoved:false} (BOTH recognition channels say live). Authored {defaultSort:{field,order}} ACCEPTED by safeParse = true. CONTROL bogus key accepted = false, so the schema really is strict and the refusal machinery works there. POSITIVE CONTROL: 7 blocks in ComponentPropsMap DO carry retiredKey tombstones (16 keys), so the zero for object-grid is a real zero, not a broken probe. Lock VERDICT command-exit 0.\n\n(2) ABLATION — direction predicted BEFORE running: the carve-out tripwire assertion shape goes RED on a tombstone and stays GREEN on a live key. Ran both legs in one run against the EXACT assertion registry-inputs-spec-parity.test.ts uses (authorableShapeKeys + toContain). LEG A (live key) GREEN: object-grid.defaultSort still authorable. LEG B (real tombstone, page:header.icon — a key retired by objectstack#6946) RED: 'AssertionError: expected [ title, subtitle, ...(8) ] to include icon'. Observed direction matched the prediction. This proves objectui's EXISTING gate fires the day upstream retires the key, with the message 'spec no longer declares object-grid.defaultSort — re-check the carve-out'. I deliberately used a real tombstone as the stand-in rather than mutating node_modules: pnpm hardlinks the content-addressable store, so editing installed dist would have reached every other agent's worktree. Mutation proved on disk by grepping injected text (grep -c on both leg markers, 1 each) with the landing site printed; restored under trap ... EXIT INT TERM with a cwd-independent rm, and absence re-confirmed after the trap fired.\n\n(3) REAL SUITES, same run — vitest exit 1 attributable solely to ablation LEG B; 115 passed, 1 failed. Green: apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (the live tripwire), packages/plugin-view/src/__tests__/ObjectView.canonicalTableKeys.test.tsx, packages/plugin-view/src/__tests__/ObjectView.sortSink.test.tsx. A red here would have meant the baseline was already broken and my no-change conclusion was hiding it.\n\n(4) TYPE-CHECK GATES (hyphenated, script name echoed to prove they ran rather than zero-matched): 'pnpm --filter @object-ui/types type-check' TYPECHECK_EXIT=0, echoed '> @object-ui/types@17.6.0 type-check'; 'pnpm --filter @object-ui/plugin-view type-check' TYPECHECK_EXIT=0, echoed '> @object-ui/plugin-view@17.6.0 type-check'. Dependency closure built first: 'pnpm --workspace-concurrency=2 --filter @object-ui/plugin-view^... build' BUILD_EXIT=0. A red would have distinguished as instructed — 'Cannot find module @object-ui/...' is the unbuilt-closure red, a missing node_modules red looks different; neither occurred.\n\n(5) ZERO-MATCH PNPM TRAP CAUGHT LIVE, reporting as instructed: 'pnpm --filter @object-ui/types^... build' printed 'No projects matched the filters' and exited 0. Not a finding — @object-ui/types is the zero-dependency protocol package (AGENTS.md section 3), so it has no closure to build. Had I read that exit 0 as a pass I would have reported building a closure that does not exist.\n\n(6) AUTHORED-OCCURRENCE RE-MEASUREMENT on my merge-base 0c282d979 (card measured at 1d9100eeb). objectui: examples/ 0, e2e/ 0, apps/ 4, content/ 8. CONTROL, identical method, a key I know IS authored (searchableFields): examples/ 4 including real authored JSON metadata at examples/schema-catalog/src/schemas/plugin-view/object-view-list.json:11 and .../plugin-grid/object-grid-columns.json:13 — the instrument works, so the zeros are real. SIBLING objectstack PROBE (it paid off decisively here): examples/ 0, apps/ 0, packages/qa/ 0 authored; the only live hits are the SPEC DECLARATION itself, which is the whole finding. Authored occurrences: ZERO, confirmed.\n\nNOT RUN, with reason: the downstream prefix-form sweep, eslint merge-base delta, and node scripts/check-changeset-presence.mjs. All three judge a diff, and the diff is empty — there is no changed file to sweep, lint, or owe a changeset for. Declared, not skipped silently.", "open_questions": [ { "question": "The direction is ruled and I am not re-opening it. What needs a decision is WHERE and WHEN the retirement lands, because the executable route crosses a repo boundary and costs an @objectstack/spec MAJOR (pin is 17.2.0, so 18.0.0) — a release-planning call the card did not anticipate.", "options": [ "A — Producer first: land objectstack#11805 (strict route per the spec-property-retirement playbook: delete the key from the strictObject shape, add the guidance-map entry, ADR-0087 conversion, exact-key RETIRED_KEYS_BY_MAJOR entry, D3 chain step, liveness ledger, generated baselines, pin tests, major changeset). objectui#5861 stays open as blocked-by and becomes mechanical on the pin bump: remove the three plugin-view reads plus ObjectGrid's two, flip the pins, drop the object-grid.defaultSort carve-out entry, tombstone the two doc pages. This is the shape objectstack#6946 plus objectui#3974 already executed end-to-end for the sibling key form.defaultSort.", "B — Consumer only, now: remove objectui's reads while the spec keeps declaring the key. Rejected on the merits: the contract still ACCEPTS an authored defaultSort (measured), so the key becomes silently dropped rather than refused. That is item 1's forbidden silence, and it is the ADR-0049 failure mode relocated rather than closed.", "C — Consumer only with a loud runtime refusal in objectui (throw or console.error on an authored table.defaultSort). Rejected: the renderer would refuse a key the pinned contract advertises as legal in its own describe text, which is declared-not-equal-enforced inverted — the exact class zod-mirror-parity.test.ts exists to catch. It also cannot move every reader, since plugin-grid is fenced by in-flight #5729, so ObjectGrid would keep honouring the key: the mirror-image divergence in the same key family at two layers.", "D — Defer: leave the key, close #5861 as not-now, revisit at the next spec major. Honest but loses the ruling's intent, and the ten-key carve-out in registry-inputs-spec-parity.test.ts keeps carrying an entry nobody owns." ], "recommendation": "A. Real business need: zero authored occurrences in BOTH repos re-measured today with a working control, so retiring costs no real user and the only thing standing is the contract advertising a key nobody writes. Long-term soundness for THIS project: contract-first (AGENTS.md #0.1) puts the fix at the producer; A is the only option that makes declared and enforced agree at the same layer, and it reuses the exact machinery objectstack#6946 and objectui#3974 already proved on the sibling key rather than inventing a renderer-side dialect. Making AI-written metadata hard to get wrong: A is the only option that yields a refusal at authoring time — the strictObject rejects the key by name with a migration prescription, which is precisely the loud failure an AI-authored metadata app needs, whereas B leaves a silently-ignored key (the worst case for a model that will read the key back and believe it took effect) and C produces a refusal that contradicts the published contract, teaching the next model that the contract cannot be trusted. Startup scope discipline: A adds no surface — it removes one, and the spec major is the honest price of an enforce-or-remove ruling rather than scope growth; B and C are cheaper today and both create a second de-facto contract, which is the expensive kind of cheap. The four axes agree here, so I am not presenting a trade-off I am hiding: the only genuine cost is the 18.0.0 bump, and that is the decision I am escalating rather than taking." } ], "out_of_scope_findings": [ "filed as objectstack#11805: [spec] ADR-0049 enforce-or-remove retirement of ObjectGridProps.defaultSort — the producer half #5861 is blocked on. Unassigned, no labels, searched for duplicates first (none; objectstack#6946 is the closed three-key precedent, not a duplicate).", "NOT filed, reported instead — PM assumption 3 needs a correction. #4648 carved object-grid.defaultSort out of the published CONSOLE REGISTRY inputs, which is true, but the key is still published in the spec's AUTHORABLE SURFACE (authorable-surface/ui.json:734, no [RETIRED] tag) and still accepted by the strictObject. So 'live-for-reading but not-for-writing' does NOT shorten the blast radius the way the dispatch expected: it is not offered as a console input, but it remains a live authorable contract key. Correcting the assumption rather than filing, since it is this card's own framing." ] }Answers to the report questions the dispatch asked by name
Which pins I flipped versus retired: none, and neither is a gap.
ObjectView.canonicalTableKeys.test.tsxandObjectView.sortSink.test.tsxpin the key as a working alias, and they are correct as written — the key still works, and must keep working while the contract still declares it. Flipping them to assert a refusal now would pin a refusal the contract contradicts. They stay live and become the flip-list for the follow-on once objectstack#11805 lands. I verified both are green today rather than assuming it.Which registries I touched and which I did not. Touched: none. Deliberately not touched, each with its reason: the ADR-0087 conversion registry and
RETIRED_KEYS_BY_MAJOR(both live in objectstackpackages/spec, and both are premature until the key actually leaves the shape);packages/spec/liveness/(same repo, and the strict route deletes the ledger row rather than marking it, so it must move with the deletion); theobject-grid.defaultSortcarve-out entry inapps/console/src/__tests__/registry-inputs-spec-parity.test.ts(its own comment forbids removing it before upstream retires — deleting it to make a retirement green is the move both that file and the playbook call out by name);content/docs/plugins/plugin-view.mdxandcontent/docs/api/schema-reference.md(prose describing a key that still works; a tombstone note there today would be false).content/docs/releases/untouched, per the standing prohibition.Separately, the two
content/docs/api/schema-reference.mdhits at lines 537-538 and 603 are not this key — they areCRUDSchema.defaultSort/defaultSortOrder, astringfield on a different carrier. Anyone sweeping this key by grep should not "fix" them.Whether an ADR edit is required, and therefore left undone: no. ADR-0049 and ADR-0087 already govern this retirement and no ADR enumerates the key (grepped
docs/adr/in objectstack — zero hits). So nothing underdocs/adr/**is owed, and the governed-surface fence never came into play.Deviations, with reasons. (1) No PR, against the dispatch's
Fixes #5861deliverable. There is no correct change to make in this repo; a PR would either fake the retirement or duplicate an existing tripwire. Had I shipped one it would have neededPart of, notFixes, since merging it must not close this card — the retirement remains owed. (2) I filed an issue in a sibling repo. The finding is this card's own blocker rather than an unrelated defect, and the durable artifact outlives this report. (3) The branchclaude/issue-5861-retire-table-defaultsortwas pushed empty as the write-route probe and carries zero commits; it is left in place as the claim landing marker. The worktree was removed cleanly without--force.
Generated by Claude Code
PM ruling — ESCALATE accepted. No code was the right answer. →
pm:blockedon objectstack#11805⛔ This is not a REWORK and ⛔ not a re-opening of the ruling. The direction — the key should go — is untouched. What the dev found is that the dispatch's location assumption was wrong, and it was wrong in a way that makes the card unexecutable on this repo's file surface.
The measurement that settles it
ObjectGridPropsSchema(objectstack/packages/spec/src/ui/component.zod.ts:2164) is astrictObjectthat still declaresdefaultSortas a live authorable key, publishes it un-retired atpackages/spec/authorable-surface/ui.json:734, and accepts an authored value today — measured, with the control that a bogus key is refused, so the strictness machinery there demonstrably works.Meanwhile objectui's own mirror (
packages/types/src/zod/objectql.zod.ts:130) is a non-strictBaseSchema.extendconsumed only by parity tests — nothing parses metadata through it at runtime.⚠️ So tombstoning it buys exactly zero refusal. That is PM assumption 4 confirmed in its strongest form: I warned that a mirror which merely stops declaring a key may buy no rejection, and here it buys none at all, because the mirror is not on any runtime path.Dispatch item 1 demanded a visible refusal, not silence. The only place that refusal can exist is the upstream
strictObject. ⇒ The card cannot be executed here.Why the two consumer-only routes are correctly rejected
- B (remove objectui's reads now): the contract still accepts an authored
defaultSort, so the key becomes silently dropped rather than refused. That is item 1's forbidden silence — the ADR-0049 failure mode relocated, not closed. - C (loud runtime refusal in objectui): the renderer would refuse a key the pinned contract advertises as legal in its own describe text — declared≠enforced inverted, which is the class
zod-mirror-parity.test.tsexists to catch. And it cannot move every reader anyway:plugin-gridis fenced by in-flight Consume the per-column sortability signal: no sort affordance on columns the platform declares unsortable — downstream leg of objectstack#10235's ruling #5729, soObjectGridwould keep honouring the key — the mirror-image divergence item 1 explicitly forbids.
✅ And objectui already states this ordering itself: the comment governing the ten ruled object-grid carve-outs in
apps/console/src/__tests__/registry-inputs-spec-parity.test.tssays they "retire only if@objectstack/specretires the keys upstream." The dispatch and I both missed that; the dev read it.The tripwire already exists, and was measured firing
⚠️ Rather than build new machinery, the dev proved the existing gate already fires the day upstream retires the key. Ablation with a real tombstone —page:header.icon, retired by objectstack#6946 — against the exact assertionregistry-inputs-spec-parity.test.tsuses: leg A (live key) green, leg B (real tombstone) red with "expected [ title, subtitle, …(8) ] to include icon". Direction predicted before running, observed as predicted.✅ And the reason a real tombstone was used instead of mutating
node_modulesis exactly right and worth repeating: pnpm hardlinks the content-addressable store, so editing installeddistwould have reached every other agent's worktree. That is a cross-agent hazard in the same family as the shared stash stack.⇒ Building a second tripwire would be duplicate machinery — the drift
packages/test-support/src/spec-tombstones.tsexists to end.Two corrections to my dispatch, both mine
⚠️ Assumption 3 was wrong. I said [Decision] spec 17.0.0 GA declares four new authorable object-* blocks and new keys on three existing ones — what does objectui publish as authoring surface? #4648 made the key "live-for-reading but not-for-writing" and that this shortens the blast radius. It does not: [Decision] spec 17.0.0 GA declares four new authorable object-* blocks and new keys on three existing ones — what does objectui publish as authoring surface? #4648 carved it out of the console registry inputs, but the key remains published in the spec's authorable surface (ui.json:734, no[RETIRED]tag) and still accepted by the strictObject. Not offered as a console input ≠ not a live authorable contract key.⚠️ A grep trap I did not warn about:content/docs/api/schema-reference.md:537-538and:603documentCRUDSchema.defaultSort/defaultSortOrder— a string field on a different carrier. ⛔ Anyone sweepingObjectGridSchema.defaultSortby grep must not "fix" them. Now on the record for the follow-on.
Also caught live, exactly as the dispatch warned:
pnpm --filter @object-ui/types^... buildprinted "No projects matched the filters" and exited 0. ✅ Read as the zero-match trap and explained (types is the zero-dependency protocol package, AGENTS.md §3), ⛔ not counted as a passing gate.Re-measurement, with a working instrument
Authored occurrences on merge-base
0c282d979: zero in both repos. objectui —examples/0,e2e/0; the 4apps/hits are the carve-out ledger and 3 of the 8content/hits are the different-carrierCRUDSchemakey. objectstack —examples/0,apps/0,packages/qa/0 authored; the only live hits are the spec declaration itself, which is the whole finding. Control by the identical method (searchableFields) found real authored JSON metadata, so the zeros are measurements.State
- objectstack#11805 filed for the producer half (duplicate-checked; objectstack#6946 is the closed three-key precedent, not a duplicate).
- This card →
pm:blockedon objectstack#11805. It becomes mechanical on the spec pin bump: remove the threeplugin-viewreads plusObjectGrid's two, flip the two pins, drop the carve-out entry, tombstone the two doc pages. ⛔ Pins stay as they are meanwhile — they correctly assert the key works while the contract declares it, and they are the flip-list for the follow-on. - No ADR edit is required — ADR-0049 and ADR-0087 already govern, and no ADR enumerates the key (
docs/adr/grepped, zero hits). So nothing is left undone on the governed surface I fenced out.
⛔ The one thing I will not decide
Retiring the key costs an
@objectstack/specMAJOR — the pin is 17.2.0, so 18.0.0. That is release planning, and ⛔ it is not the PM seat's call. It goes to the maintainer list as item ⑪, with the dev's recommendation attached: route A, producer first, on the grounds that zero authored occurrences in both repos means retiring costs no real user; contract-first (AGENTS.md #0.1) puts the fix at the producer; it reuses the machinery objectstack#6946 + objectui#3974 already proved on the sibling keyform.defaultSort; and it is the only route that yields a refusal at authoring time — which for an AI-authored metadata platform is the whole point, since a silently-ignored key is the worst case for a model that reads it back and believes it took effect.
Generated by Claude Code
- B (remove objectui's reads now): the contract still accepts an authored
Unlock scan: producer half has LANDED — and the escalated cost question is already answered
Two updates on this card, both measured.
1. The blocker is resolved upstream
objectstack#11805 is closed / completed, landed by merged PR objectstack#12256 (
feat(spec): retire object-grid's legacy defaultSort fallback, merged 2026-08-25). Measured on objectstackorigin/main:packages/spec/src/ui/component.zod.ts:2299now declaresdefaultSort: retiredKey(...)with migration guidance namingsortand the array wrapping.packages/spec/authorable-surface/ui.json:723publishesui/ObjectGridProps:defaultSort [RETIRED]. Positive control in the same file: the siblingui/FormView:defaultSort [RETIRED]sits at line 455, so the[RETIRED]tag is a spelling this surface really uses and the reading is not a pattern artefact.
2. ⭐ The
majorquestion this card escalated has been ANSWERED — it can come off the maintainer listThe prior PM ruling here escalated one thing and one thing only: that retiring the key looked like it cost an
@objectstack/specMAJOR (17.2.0 → 18.0.0), which is release planning and not a seat's call. It went to the maintainer list as item ⑪.That is settled.
component.zod.ts:2279records the maintainer's ruling verbatim:「#11805 退役 defaultSort,不需要major」
The retirement took the
retiredKey()tombstone route rather than the strict-delete route, which is why no major was needed. So the escalation is closed by the ruling, and item ⑪ should be struck from the decision box — no maintainer input is outstanding on this card.Worth recording why the earlier reasoning went wide: the dispatch and the dev both reasoned from the
spec-property-retirementplaybook's strict route, which does cost a major. The playbook has a second route, and the maintainer picked it. A cost derived from one route of a playbook is not the cost of the outcome.3. But this card still does NOT return to the queue
The retirement is merged and unpublished, so the same discipline that parked #5987 and #6246 applies:
- Newest published
@objectstack/specis 17.2.0, published 2026-08-23T07:01:46Z; PR objectstack#12256 merged 2026-08-25. Maximumpublished_atacross the 100 most recent objectstack releases is 2026-08-23T07:02:23Z, with no release on or after 2026-08-24. - This repo resolves
@objectstack/spec@17.2.0inpnpm-lock.yaml.
⚠️ And this card hits the version-collision hazard in its sharpest form: objectstackmain'spackages/spec/package.jsonstill reads17.2.0— the identical string to the published release that does not carry the tombstone. A restart criterion phrased as a version comparison would fire a false unlock and send a dev to delete reads while the installed contract still accepts the key. That is precisely the "silently dropped rather than refused" outcome this card's own item 1 forbids.pm:blockedis therefore replaced bypm:on-hold.Restart-when:
grep -c "ObjectGridProps:defaultSort \[RETIRED\]" node_modules/@objectstack/spec/authorable-surface/ui.jsonreturns 1 in this repo after a spec pin bump — i.e. the installed spec carries the tombstone. ⛔ Do not substitute a version comparison.Restart-touch:
pnpm-lock.yaml.On restart, the work is mechanical and the shape is already written
Per the prior ruling, unchanged: remove the three
plugin-viewreads plusObjectGrid's two, flip the two pins (ObjectView.canonicalTableKeys.test.tsx,ObjectView.sortSink.test.tsx) from "works as an alias" to "refused", drop theobject-grid.defaultSortcarve-out entry inapps/console/src/__tests__/registry-inputs-spec-parity.test.ts, and tombstone the two doc pages. Pins stay as they are until then — they correctly assert the key works while the installed contract still declares it.Two traps already paid for, carried forward so they are not re-hit:
content/docs/api/schema-reference.md:537-538and:603documentCRUDSchema.defaultSort/defaultSortOrder— a string field on a different carrier. ⛔ A grep sweep for this key must not "fix" them.- The existing tripwire in
registry-inputs-spec-parity.test.tswas measured firing against a real tombstone, so no new gate is owed; it will go red on the pin bump with "spec no longer declaresobject-grid.defaultSort— re-check the carve-out".
Generated by Claude Code
1 remaining item
Released from
pm:on-hold→pm:queueby the #8773 hold audit (PM sessionsession_019wtfW1ZxGnP1XKGc9uZVms, 2026-09-09T06:5xZ).Why the hold was never right: this card is not waiting on an event — it is the work a maintainer ruling explicitly ordered opened. From #4869's decision digest (maintainer 2026-08-22, verbatim 「接受所有」):
Option A now, plus "the follow-up card for the C half — an ADR-0049 enforce-or-remove retirement of
table.defaultSortas its own card, not folded into this one."Option A has landed (
claude/issue-4869-objectview-sort-sink). Its predecessor is done and the ruling that directed this half is on the record. A card carrying a live ruling with its prerequisite already merged belongs in the queue, not in a hold with no wake condition.⛔ It carried no
Restart-when:and noBlocked-by:, so nothing was ever going to return it — it has sat since 2026-08-23.Scope on pickup: the ADR-0049 enforce-or-remove retirement of
ObjectGridSchema.defaultSort(declared{ field, order }, already marked@deprecated Use sort insteadinpackages/types/src/objectql.ts) — as its own change, per the ruling's explicit "not folded into this one".⚠️ Two things to establish before writing code, neither of which this seat has verified:- Re-read [finding] ObjectView 是最后一处不走共享 sort sink 的读点 ——
$orderby: sort原样透传,归一化外包给了某一个 adapter #4869's landed Option A on currentmain— what it did and deliberately did not do bounds this half. defaultSortis a published key. Under 「创业阶段不渐进」 a retirement ships immediately without a staged window, but if removal turns out to touch an accept set beyond the deprecation already declared, stop and report rather than widening scope.
Generated by Claude Code
- Re-read [finding] ObjectView 是最后一处不走共享 sort sink 的读点 ——
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actions解锁:重启条件已经满足,剩下的是机械工作 —— 撤销过期的认领,定级
priority:p3,留在队列等派发分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T05:50Z。objectui 清理第六批(#8587 的挂起卡一次性清点):本席读完了卡面和评论(6 条,全部读到),并在本地 main 上重新测量。本卡有指派人,本席先报给维护者。维护者答:「同意」。方向早已裁定,不再讨论
维护者 08-22 在 #4869 的裁定(「接受所有」)要求
table.defaultSort按 ADR-0049"要么执行,要么删除"的原则退役。重启条件已经满足
5450800597写的重启条件是:已安装的 spec 带有ObjectGridProps:defaultSort [RETIRED]墓碑,不能用版本号比较来代替。本地 main(09-21)实测:pnpm-lock.yaml解析到的@objectstack/spec是 17.4.0。apps/console/src/__tests__/registry-inputs-spec-parity.test.ts:971已经记录:object-grid.defaultSort是在 17.3.0 被收走的第九个墓碑,spec 在safeParse时按名拒绝它。那条豁免项也已经删掉了。
⇒ 契约层面已经按名拒绝这个字段,objectui 这边剩下的读取都是死代码,读的是一个契约不接受的字段。
剩下的工作(
5398700176早已写好,本席按现状核对过)- 删除仍在读取这个字段的代码:
packages/plugin-view/src/ObjectView.tsx(如:1038的schema.table?.defaultSort)、packages/plugin-grid/src/ObjectGrid.tsx、packages/plugin-grid/src/index.tsx。⚠️ 所有读取点要一次移完,不能只改一处。 - 把
ObjectView.canonicalTableKeys.test.tsx和ObjectView.sortSink.test.tsx里断言"这个字段能用"的测试,改写为断言"这个字段不再生效"(契约层已经按名拒绝它)。⛔ 不删。同一轮里断言规范写法sort在三条路径上都照常生效。 - 给
content/docs/plugins/plugin-view.mdx和content/docs/api/schema-reference.md里描述这个字段的段落加上墓碑说明。⚠️ schema-reference.md:537-538和:603写的是CRUDSchema.defaultSort,那是另一个载体上的字符串字段,⛔ 不要动。
认领与定级
- 08-24 的认领(
5398412525)停下来是对的:当时 spec 那一半还没落地(5398671726),无从下手。之后一个月没有新动静,本席撤销这个认领,让卡可以重新派发。这不是否定当时的工作,那份报告正是今天能直接执行的原因。 priority:p3:收掉一个契约已经拒绝的旧写法,用户看不到任何变化。保留pm:queue。
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsClaim: PM loop round 4 —
domain:uiexecution seat 2
Session:session_014mXUNuFomfj24w7s1pZzhN
Branch:claude/issue-5861-retire-table-defaultsort
Worktree:objectui-issue-5861
Domain:domain:ui
Seat:domain:ui#2
File surface: every remaining read of the retireddefaultSorton the grid and view paths:packages/plugin-view/src/ObjectView.tsx(schema.table?.defaultSortand its comments),packages/plugin-grid/src/ObjectGrid.tsx(the fetch path'sschema.defaultSortarm and theschemaSort ?? defaultSortprecedence), andpackages/plugin-grid/src/index.tsx(the registration docblock). It also covers the two view tests that assert the key works (ObjectView.canonicalTableKeys.test.tsx,ObjectView.sortSink.test.tsx), which are rewritten to assert it no longer applies rather than deleted. And it covers thedefaultSortparagraphs incontent/docs/plugins/plugin-view.mdxandcontent/docs/api/schema-reference.md, tombstoned, leaving untouched theCRUDSchema.defaultSortrows, which are a different carrier. One.changeset/5861-…md. Added by the seat after the dev report, because the order's H1 put them in scope or because the change made their shipped text or pins false:@object-ui/types'ObjectGridSchema.defaultSortinpackages/types/src/objectql.tsand its zod mirror inpackages/types/src/zod/objectql.zod.ts(defaultSortonly, retired as a tombstone), with a new types pin beside them;packages/plugin-view/README.md(it described the key as a working alias);- the view tests
ObjectView.filterSources.test.tsx/ObjectView.namedViewSortArity.test.tsxand the grid testsgridArrayArmOrderby-8973,gridDefaultFiltersLowering,gridRetiredStringSort-8767,serverSorting, which pinned the key as working.
Open-PR hunks compared at the seat's amendment:
objectql.ts/objectql.zod.ts: this seat's PR objectui#10522 holds theObjectDataTableSchemaregion, and draft PR objectui#8941 holdsBulkActionDef. Both are region-disjoint fromObjectGridSchema.defaultSort.schema-reference.md: objectui#8941 holds the detail-view section; this PR holds the grid section.ObjectGrid.tsx: the parked objectui#10278, as above.
Added by the seat after the contract review's rework: the five pending changesets whose sentences this retirement falsifies, each dated in the precedent form of PRs objectui#9139 / #9723 / #9734:
objectview-sort-sink-4869.md,6235-mergedsort-wrap.md,8973-grid-orderby-normalize.md,grid-default-filters-lowering-4082.md,5293-view-sort-order-spelling.md.No other open PR touches these files (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus—dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」; executes the 2026-08-22 ruling on objectui#4869 (the C half: ADR-0049 enforce-or-remove, remove arm), with the restart condition that triage5808485875measured
Clause-②: yes
Thread-read: 5808485875
Serial constraints cleared: open-PR file lists read 2026-09-25T06:26Z.plugin-view/src/ObjectView.tsxwas last held by PR objectui#10494 (objectui#10035), merged3759c568e.ObjectGrid.tsxis also on this seat's parked draft PR objectui#10278 (page-size, objectui#9853 in the decision box); the later lander merges. No other open PR touches these files. Livepm:dispatchedclaims of seats 1, 3 and 4 name none of them.Clause-②: yes— this removes the last runtime reads and the declared docs of a key the installed spec 17.4.0 already refuses by name. The published grid/view props surface narrows.
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{ "issue": 5861, "status": "done", "branch": "claude/issue-5861-retire-table-defaultsort", "pr": "https://github.com/objectstack-ai/objectui/pull/10533", "session": "session_014mXUNuFomfj24w7s1pZzhN — mode:subagent, the dispatching PM session id (Claude-Session trailer on every commit)", "premise_still_valid": true, "summary": "Retired object-grid defaultSort / object-view table.defaultSort under ADR-0049 (remove arm) in one change: every reader goes together. ObjectGrid lost its fetch arm and its header fallback. ObjectView lost the non-grid lowering, the grid forwarding and the delegated mergedSort branch. The plugin-grid sort input description no longer advertises it. H1 decision: the TypeScript declaration goes too, as a tombstone, not a deletion. ObjectGridSchema.defaultSort is now `?: never` and the zod mirror member is a retirementTombstone(), in lockstep, because BaseSchema's index signature and .passthrough() would otherwise absorb a deleted key silently. This is the package's convention (viewTabBar, objectui#7779). A downstream tsc through the rebuilt dist now refuses the key with TS2322. H2 falsified nothing: the installed spec 17.4.0 refuses the key by name, no other source reads it, and authored occurrences are zero outside the docs tombstoned here. One authored occurrence the earlier reads missed: the ObjectGridSchema JSON example in schema-reference.md authored the key, and now authors sort. Card assignee was os-elon-musk at pickup and was not written; the PR assignee was set to it. The PR stays draft.", "tests": "RED ON BASE / GREEN ON HEAD (the H3 pins). The same 9 files were run twice. HEAD run (tests commit 43ab1fd43): 'Test Files 9 passed (9) / Tests 116 passed (116)', lock VERDICT command-exit 0. || Base leg: the 5 source files were put back to base 59636b3a6 under the committed tests, with a trap restore on absolute paths. Mutation proved on disk: before, `schema.table?.defaultSort` count 0 and `defaultSort?: never` count 1. After, 3 / 0, plus `else if (schema.defaultSort)` 1 and `defaultSort: z.object` 1. Result: 'Test Files 8 failed | 1 passed (9) / Tests 19 failed | 97 passed (116)', VITEST_EXIT=1. Every one of the 19 reds is a flipped or new defaultSort cell; all canonical-sort controls stayed green. Restore proved: 'RESTORE VERIFIED (blob hashes match HEAD for all 5 files)', `git diff HEAD` empty. || The 19 reds: canonicalTableKeys (5): no longer forwards a retired table.defaultSort; puts the canonical value in the canonical slot; keeps the named view sort in force and a retired defaultSort beside it is not forwarded; a retired table.defaultSort alone orders NOTHING; hands over NO sort for a retired table.defaultSort alone. sortSink (3): sends no $orderby at all; a descending legacy default is inert too; keeps the sink STRICT. filterSources (3): the three objectui#6235 wrap cells, flipped. namedViewSortArity (1): a retired table.defaultSort sorts NOTHING on the real grid path, no $orderby and no arrow. gridArrayArmOrderby-8973 (2). gridDefaultFiltersLowering (1). serverSorting (1, new): a retired defaultSort lights NO arrow and sends NO $orderby. types object-grid-default-sort-tombstone-5861 (3, new): refused by name; remedy text; describe equals message. || BUILD: `turbo run build --filter='@object-ui/plugin-view^...' --concurrency=2` gave 'Tasks: 15 successful, 15 total', lock VERDICT command-exit 0. packages/types/dist/objectql.d.ts carries `defaultSort?: never`. || TYPE-CHECK (hyphenated script names echoed): @object-ui/types exit=0 ('> @object-ui/types@17.6.0 type-check'); @object-ui/plugin-grid exit=0; @object-ui/plugin-view exit=0; lock VERDICT command-exit 0. `--listFiles` shows each tsconfig.test.json includes the new types test (1) and the 4 plugin-view and 4 plugin-grid test files, so the new @ts-expect-error on the `?: never` twin is compiled and used. || REVERSE TYPE CHECK through the rebuilt .d.ts, predicted red. ablation-replace injected `defaultSort: { field: \"name\", order: \"asc\" }` into ObjectView's gridSchema literal ('ok mutation landed: anchor 1 -> 0, blob e644e3b9d192 -> ab53e7ffea30'). `plugin-view tsc --noEmit` then failed: 'error TS2322 ... Types of property defaultSort are incompatible. Type { field: string; order: string; } is not assignable to type undefined'. Restored: 'ok restored: blob == HEAD (e644e3b9d192) and git diff HEAD is empty'. The first attempt was a no-op: the tool refused before mutating or running anything because the replacement contained its own anchor. The anchor was changed and the leg re-run. || PACKAGE SUITES, root form, at HEAD f40819d61 (after that commit only the changeset text changed): packages/types/ 'Test Files 233 passed (233) / Tests 5166 passed (5166)'; packages/plugin-grid/ '156 passed (156) / 1487 passed (1487)'; packages/plugin-view/ '48 passed (48) / 444 passed (444)'. Also apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, which reads the plugin-grid inputs: '199 passed'. Every run: lock VERDICT command-exit 0. || GATES (exit codes). check-changeset-presence 0: '14 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'. check-changeset-no-major 0. check-changeset-fixed 0. check:new-line-citations 0: 'VERDICT new-cross-file-line-citations: 0 new citation(s)'. check:control-bytes 0. check-doc-links 0. check:doc-types 0. check:doc-fences 0. check:doc-example-ids 0. check-prompt-component-keys 0. check-doc-expression-carriage 0 (report-only; its schema-reference site action.condition predates the branch). check-changeset-claims 0 (report-only). check:pending-changeset-literals 0. check-governed-queue-guard --test on all 18 paths: 'NOT GOVERNED'. || ESLINT, narrowed to the 14 changed .ts/.tsx files with --format json: 14 files reported, 0 errors, and no 'ignored' notice. Per-file warnings match the base except three test files (+1, +1, +2, all no-explicit-any from `as any` fixtures). Invariance: eslint.config.js has no parserOptions.project, projectService or type-checked preset, and no eslint-rules/ rule reads the filesystem, so the verdict on an untouched file cannot move. || NOT MEASURED: check:doc-snippets / check:doc-examples (they build every snippet-imported package; this diff adds no TS snippet). Reason: out of the foreground budget; declared to CI. Repo-wide `pnpm lint` and `pnpm test` belong to CI.", "mcp_calls": "0 — none", "api_writes": "3. (1) pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches, which executed POST /repos/objectstack-ai/objectui/pulls, draft forced (run 36107123158, success), giving PR 10533. (2) label-write --assign os-elon-musk through the relay: POST /repos/objectstack-ai/objectui/issues/10533/assignees (run 36107184676), with read-back 'MATCHES the target'. (3) this os-dev-report comment through post-stamped: POST /repos/objectstack-ai/objectui/issues/5861/comments. Not REST: 6 git push calls (1 rejected probe, see deviations; 5 accepted).", "files_changed": [ "packages/types/src/objectql.ts", "packages/types/src/zod/objectql.zod.ts", "packages/types/src/__tests__/object-grid-default-sort-tombstone-5861.test.ts (new)", "packages/plugin-grid/src/ObjectGrid.tsx", "packages/plugin-grid/src/index.tsx", "packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx", "packages/plugin-grid/src/__tests__/gridDefaultFiltersLowering.test.tsx", "packages/plugin-grid/src/__tests__/gridRetiredStringSort-8767.test.tsx", "packages/plugin-grid/src/__tests__/serverSorting.test.tsx", "packages/plugin-view/src/ObjectView.tsx", "packages/plugin-view/src/__tests__/ObjectView.canonicalTableKeys.test.tsx", "packages/plugin-view/src/__tests__/ObjectView.sortSink.test.tsx", "packages/plugin-view/src/__tests__/ObjectView.filterSources.test.tsx", "packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx", "packages/plugin-view/README.md", "content/docs/plugins/plugin-view.mdx", "content/docs/api/schema-reference.md", ".changeset/5861-retire-table-defaultsort.md (new; minor on types, plugin-grid and plugin-view)" ], "deviations": [ "File surface beyond the claim, and its reasons (stated in the PR). packages/plugin-view/README.md described the key as a working alias and ships in files[]; this change made it false, and the role file requires repairing what the change falsifies. Four more plugin-view/plugin-grid test files pinned the key as working, or referenced it; they sit next to the landing points. The order says to stop on any other breach; I read these as consequences of the named landing points, not a new surface. The conflict is flagged here, not settled silently.", "schema-reference.md: the CRUDSchema sentence is untouched. The adjoining sentence 'ObjectGridSchema declares its own defaultSort; that key is unaffected' became false, and was rewritten. The ObjectGridSchema JSON example authored the retired key, and now authors sort.", "plugin-view.mdx: cross-file line-address citations in the touched paragraphs (ObjectView.tsx :403/:420 and :604-611 etc., Registry.ts :194/:226) were re-cited by content per AGENTS.md #11. The gate reads 0 new citations.", "The first commit (9f3f52823) carries the harness-supplied model-named Co-Authored-By trailer instead of the model-free pair. objectui has no pre-push hook that refuses it. History is not rewritten (force-push is banned in objectui). The later commits use the model-free pair.", "Write-route probe: the first `git push -u` of the empty branch was rejected ('! [rejected] (fetch first)', plus 'RPC failed; HTTP 403'). The remote branch still pointed at the previous dev's empty probe tip 0c282d979, which this shallow checkout did not hold. REST compare shows base-to-main is 'ahead_by 2122 behind_by 0', so it was an ancestor. The shared objectui .git was deepened with `git fetch --deepen=2200 origin main` (is-ancestor then exit 0), and the push fast-forwarded 0c282d979..59636b3a6. No force.", "The card's newest Claim (5827924558) names this branch, confirmed before the first edit. The card has 8 comments, not the 7 the order says; all 8 were read. Worktree removed without --force after rm -rf node_modules. Private refs refs/os-5861/* deleted. No background process or monitor is left.", "The pending changesets for objectui#4869, #6235, #8973 and the #4082 grid filter-lowering one describe how the retired key used to be honoured, and they ship in the same release. The new changeset says it supersedes them. They are not edited (another card's text)." ], "open_questions": [], "out_of_scope_findings": [ "class: a · content/docs/api/schema-reference.md, ObjectGridSchema property table: the `sort` row declares type `string | SortConfig[]`. The string clause is retired in this renderer (objectui#8221): ObjectGridSchema.sort is SortConfig[] only, ObjectGrid refuses a string sort at runtime with a console.error and sends no $orderby (pinned by packages/plugin-grid/src/__tests__/gridRetiredStringSort-8767.test.tsx), and tsc rejects it. So an author following the row gets an unsorted grid. Upstream @objectstack/spec 17.4.0 still accepts `sort: \"name asc\"` on ObjectGridProps (measured safeParse success; the array control is also green), so this renderer is deliberately narrower than the spec, and the doc teaches the wider spelling. · dedupe words: schema-reference sort string SortConfig object-grid; retired string sort clause docs; 8221 docs table · carrier: none (承接者:无) — noted, not filed" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{ "issue": 5861, "status": "done", "branch": "claude/issue-5861-retire-table-defaultsort", "pr": "https://github.com/objectstack-ai/objectui/pull/10533", "head": "28b1a4d3245b746be1fa6323b734a8eba71665f5", "session": "session_014mXUNuFomfj24w7s1pZzhN — mode:subagent, the dispatching PM session id (Claude-Session trailer on every commit)", "premise_still_valid": true, "summary": "REWORK round for the FAIL contract review at f40819d61 (PR comment 5828759786, read in full). New head: 28b1a4d32, one commit on top, pushed, CI started on it. (1) The required Test check was red on scripts/__tests__/doc-version-claims.test.ts. The doc tombstones had added unregistered `@objectstack/spec` version literals: plugin-view.mdx and plugin-view/README.md, plus a third in schema-reference.md split across a line break, which the gate cannot see. The gate's preferred remedy was applied to all three: the version literal is deleted, and each page now says the installed protocol refuses the key by name. No KNOWN_CLAIMS entry was added. (2) Five pending changesets that would publish 'keeps working' beside 'no longer read' in the same release were reconciled in the precedent form of PRs #9139, #9723 and #9734. The falsified sentence goes into the past tense for its own change, and a dated note (2026-09-25) names objectui#5861. The five: objectview-sort-sink-4869, 6235-mergedsort-wrap, 8973-grid-orderby-normalize, grid-default-filters-lowering-4082, 5293-view-sort-order-spelling. The retirement changeset's supersedes paragraph now names all five. (3) Nit: the objectui#5270 comment in ObjectView.tsx no longer quotes the retired readers in the present tense. No merge of main: no conflict, and no CI need. Code, tests, tombstone and semver level are unchanged from the reviewed head.", "tests": "REPRODUCED RED at f40819d61: `pnpm -C WORKTREE exec vitest run scripts/__tests__/doc-version-claims.test.ts` gave 'Tests 1 failed | 28 passed (29)', lock VERDICT command-exit 1. The message named both visible sites: 'content/docs/plugins/plugin-view.mdx:140 \"@objectstack/spec` 17.3.0\"' and 'packages/plugin-view/README.md:209 \"@objectstack/spec` 17.3.0\"'. || GREEN at 28b1a4d32, same command: 'Test Files 1 passed (1) / Tests 29 passed (29)', lock VERDICT command-exit 0. The gate-invisible third site: every `+` line of `git diff 59636b3a6 HEAD -- content/ packages/plugin-view/README.md` was grepped for a version-shaped token; grep exit 1, no match. || plugin-view at 28b1a4d32: closure build `turbo run build --filter='@object-ui/plugin-view^...'` gave 'Tasks: 15 successful, 15 total / Cached: 15 cached' (types and plugin-grid sources unchanged since the reviewed head; dist carries `defaultSort?: never`). `pnpm --filter @object-ui/plugin-view type-check` exit=0 ('> @object-ui/plugin-view@17.6.0 type-check'). vitest `packages/plugin-view/` plus doc-version-claims: 'Test Files 49 passed (49) / Tests 473 passed (473)' (plugin-view 48/444, plus 1/29). Lock VERDICT command-exit 0. eslint on ObjectView.tsx: 0 errors, 60 warnings (base 60). || GATES at 28b1a4d32, all exit 0. check-changeset-claims (report-only): 'Every package declared across those 6 body(ies) is either not negated'; the defaultSort sentences in the 50 flagged pending entries were re-read. check-pending-changeset-literals: 'No test source names a pending changeset'. check-changeset-presence: '14 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'. check-changeset-no-major. check-changeset-fixed. check:new-line-citations: 'VERDICT new-cross-file-line-citations: 0 new citation(s)'. check:control-bytes. check-doc-links. check:doc-types. check:doc-fences. check:doc-example-ids. check-prompt-component-keys. check-doc-expression-carriage (report-only). || Carried from the reviewed head, unchanged sources, not re-run: types suite 233/5166 and plugin-grid suite 156/1487 at f40819d61, types and plugin-grid type-check exit 0, red-on-base leg 19 failed / 97 passed then 116/116 green, reverse type check TS2322. This round touched no source in those packages. || CI on the new head 28b1a4d32 had started when read: check-runs total_count 42, 19 in_progress, 20 success, 3 skipped. Changeset Declaration success. Test shards 1-8 in_progress. Reported without waiting on CI, per the contract.", "mcp_calls": "0 — none", "api_writes": "This round: 1 — the os-dev-report comment through post-stamped (fleet-write relay): POST /repos/objectstack-ai/objectui/issues/5861/comments. The PR body is NOT patched; the replacement is in pr_body_replacement for the seat to apply. Not REST: 1 accepted git push (f40819d61..28b1a4d32). Reads only: GET of the review comment, of the PR's pending state and of check-runs on the new head. Earlier round (already reported): 3 writes (pr_create, label-write assign, report comment).", "files_changed_this_round": [ "content/docs/plugins/plugin-view.mdx (version literal removed)", "packages/plugin-view/README.md (version literal removed)", "content/docs/api/schema-reference.md (line-split version literal removed)", "packages/plugin-view/src/ObjectView.tsx (comment only: objectui#5270 block in the past tense)", ".changeset/objectview-sort-sink-4869.md (dated note; falsified sentences in the past tense)", ".changeset/6235-mergedsort-wrap.md (dated note; falsified sentences in the past tense)", ".changeset/8973-grid-orderby-normalize.md (dated note)", ".changeset/grid-default-filters-lowering-4082.md (dated note)", ".changeset/5293-view-sort-order-spelling.md (defaultSort cited as retired at this change)", ".changeset/5861-retire-table-defaultsort.md (supersedes paragraph names all five)" ], "deviations": [ "Overruled deviation withdrawn: the earlier round said 'another card's text' and left the pending changesets unedited. That is reversed per the review and the PR precedent; all five are reconciled in this PR.", "Pending changesets re-grepped: three further defaultSort mentions were left unedited because they remain true. 7122-spec-17-3-0-pin-repointing says the spec made object-grid.defaultSort a D2 tombstone. 8767-object-grid-refuses-string-sort quotes the spec's retirement text. 6697-rekey-three-fetch-effects-on-primitives is about RelatedList's own defaultSortKey, a different carrier.", "Worktree recreated from the pushed branch (tip f40819d61, no foreign pushes) and removed again without --force after rm -rf node_modules. Private ref refs/os-5861/remote-tip deleted. No background process or monitor is left. Shell cwd changes used `env --chdir` / `pnpm -C` / `git -C`, with no `cd … &&` compounds.", "The first commit 9f3f52823 still carries the harness-supplied model-named trailer. The review judged this not a blocker: squash merges use the PR title and body. It was not rewritten.", "PR body now out of date in two places: it said the pending changesets are not edited, and it has no rework section. pr_body_replacement carries the corrected full body. It keeps `Fixes #5861` as line 1 and a line starting `Clause-②: yes`; hazard-scanned: 0 angle brackets, no model identifier, the only closing keyword is on #5861." ], "open_questions": [], "out_of_scope_findings": [ "class: a · (carried from the first round; the review confirmed it at source) content/docs/api/schema-reference.md ObjectGridSchema property table, `sort` row typed `string | SortConfig[]`, while ObjectGridSchema.sort is SortConfig[] only and the renderer refuses a string (objectui#8221; pinned by gridRetiredStringSort-8767.test.tsx). Spec 17.4.0 still accepts the string. · dedupe words: schema-reference sort string SortConfig object-grid; retired string sort clause docs; 8221 docs table · carrier: none (承接者:无) — noted, not filed" ], "pr_body_replacement": "Fixes #5861\nClause-②: yes — this removes the last runtime reads and the declared docs of a key the installed `@objectstack/spec` 17.4.0 already refuses by name; the published grid/view props surface narrows.\n\n## What this does\n\nADR-0049 enforce-or-remove, remove arm, for `object-grid`'s `defaultSort` and `object-view`'s `table.defaultSort` — the \"C half\" of the maintainer's 2026-08-22 ruling on objectui#4869 (「接受所有」), restart condition measured by triage comment 5808485875.\n\nEvery reader is removed in one change, so no path honours the key while another ignores it:\n\n| Reader | Before | After |\n| --- | --- | --- |\n| `ObjectGrid` fetch path | third arm `else if (schema.defaultSort)` lowered it to `$orderby` | chain reads `sort` only |\n| `ObjectGrid` header arrows | `parseSchemaSort(schemaSort ?? [schema.defaultSort])` | `parseSchemaSort(schemaSort)` |\n| `ObjectView` non-grid fetch | fourth branch lowered `table.defaultSort` into the sink | chain ends at `table.sort` |\n| `ObjectView` grid forwarding | `defaultSort: schema.table?.defaultSort` | not forwarded |\n| `ObjectView` delegated `renderListView` | `mergedSort` wrapped `table.defaultSort` into an array | chain ends at `table.sort` |\n| `plugin-grid` registration | `sort` input described `defaultSort` as its fallback | description says it is retired |\n\n## H1 — the TypeScript declaration goes too, as a tombstone (decided and measured)\n\n`ObjectGridSchema.defaultSort` becomes `defaultSort?: never` and the zod mirror member becomes `retirementTombstone(...)`, in lockstep. Not a deletion: `ObjectGridSchema` extends `BaseSchema`, whose `[key: string]: any` absorbs an undeclared key as `any`, and the mirror's `.passthrough()` keeps it unexamined, so deleting would let the retired spelling type-check and parse green and do nothing. This is the package's existing convention (`ObjectViewSchema.viewTabBar`, objectui#7779). Measured on the rebuilt `dist`: a downstream `ObjectGridSchema` literal carrying `defaultSort` fails `tsc` in `plugin-view` with TS2322 (reverse check below). Only the `defaultSort` regions of `objectql.ts` / `objectql.zod.ts` were touched; `ObjectGridSlotKey` keeps listing the key because the member is still declared.\n\n## H2 — no live producer (measured, with lit controls)\n\n- Installed spec 17.4.0: `ObjectGridPropsSchema.safeParse({ objectName, defaultSort })` fails `invalid_type` at `defaultSort` with the message \"`object-grid` property `defaultSort` was removed in @objectstack/spec 17 (ADR-0049) — it was the legacy second spelling of `sort` …\"; control `{ objectName, sort: [...] }` parses green.\n- No non-test source outside the edited files reads the key (`git grep defaultSort -- packages/` — the remaining hits are `plugin-detail`'s own `RelatedList` `defaultSort` prop, a different carrier fed from `schema.sort`, plus CHANGELOGs). `normalize-list-view` never mentions it.\n- Authored occurrences — objectui: `examples/` 0 files, `e2e/` 0, `apps/` 1 (the registry parity test's comments, not authoring), `content/` 2 (the two pages tombstoned here). objectstack `origin/main`: `examples/` 0, `apps/` 0, `packages/qa/` 0. Lit control, identical method, `searchableFields`: objectui `examples/` 4 files, `content/` 5; objectstack `examples/` 2, `packages/qa/` 1.\n- ⚠️ One authored occurrence the earlier reads did not flag: the `ObjectGridSchema` JSON example in `content/docs/api/schema-reference.md` authored `\"defaultSort\": { \"field\": \"name\", \"order\": \"asc\" }` — a copy-paste of it is refused by the spec. It now authors `\"sort\": [{ \"field\": \"name\", \"order\": \"asc\" }]`.\n\n## H3 — pins, red on the base and green on the head\n\nThe flipped cells were rewritten, not deleted: each now asserts the key is inert on its path, beside a canonical `sort` control on the same path in the same run.\n\nRed leg: the five source files were put back to the base `59636b3a6` under the committed tests, with a trap restore (blob hashes re-checked against HEAD afterwards, `git diff HEAD` empty): **19 failed | 97 passed (116)**, 8 of 9 files red. The only all-green file is `gridRetiredStringSort-8767.test.tsx`, whose cell was only re-commented. Green leg on the head: **116 passed (116)**, 9 files.\n\nFlipped or new, each red on base:\n\n- `plugin-view` `ObjectView.canonicalTableKeys.test.tsx` — grid path: `no longer forwards a retired table.defaultSort — and does not rescue it into sort`; `puts the canonical value in the canonical slot…` (legacy slot now undefined); `keeps the named view sort in force, and a retired table.defaultSort beside it is not forwarded`. Non-grid: `a retired table.defaultSort alone orders NOTHING`. Delegated: `hands over NO sort for a retired table.defaultSort alone`.\n- `plugin-view` `ObjectView.sortSink.test.tsx` — `sends no $orderby at all…`, `a descending legacy default is inert too…`, and the tail of `keeps the sink STRICT…` (the sink-strictness half is unchanged).\n- `plugin-view` `ObjectView.filterSources.test.tsx` — the three objectui#6235 wrap cells, flipped.\n- `plugin-view` `ObjectView.namedViewSortArity.test.tsx` — through the REAL `ObjectGrid`: `a retired table.defaultSort sorts NOTHING on the real grid path — no $orderby, no arrow`, plus a new canonical `table.sort` control cell.\n- `plugin-grid` `gridArrayArmOrderby-8973.test.tsx` — the legacy-arm section, flipped (fully-specified, and `order` omitted, now send no `$orderby`).\n- `plugin-grid` `gridDefaultFiltersLowering.test.tsx` — `a retired defaultSort reaches neither $orderby nor $filter`.\n- `plugin-grid` `serverSorting.test.tsx` — new: `a retired defaultSort lights NO arrow and sends NO $orderby`, with a sortable-chrome non-vacuity check.\n- `types` `object-grid-default-sort-tombstone-5861.test.ts` — new: refused by name (`invalid_type` at `defaultSort`), lit control (unknown key green), remedy text, describe equals message, canonical `sort` green, upstream re-derivation from the installed pin, and the `@ts-expect-error` on the `?: never` twin (compiled by `tsconfig.test.json`; `--listFiles` includes the file).\n\nCanonical `sort` is asserted on all three `ObjectView` paths and on bare `ObjectGrid` in the same runs.\n\n## Review rework (head `28b1a4d32`)\n\nThe contract review at `f40819d61` (comment 5828759786) returned FAIL on two items and one nit. All three are addressed in `28b1a4d32`:\n\n1. **Required `Test` was red** on `scripts/__tests__/doc-version-claims.test.ts` (\"records every version literal on the scanned surfaces\"). The doc tombstones had added unregistered `@objectstack/spec` version literals in `plugin-view.mdx` and `plugin-view/README.md`, plus a third in `schema-reference.md` split across a line break, which the gate cannot see. The gate's preferred remedy is applied to all three: the literal is deleted, and each page now says the installed protocol refuses the key by name. No `KNOWN_CLAIMS` entry is added. Reproduced red locally first (`Tests 1 failed | 28 passed (29)`, naming both visible sites), then green (`29 passed (29)`). No line this PR adds under `content/` or `packages/plugin-view/README.md` carries a version-shaped token.\n2. **Pending changesets that would publish \"keeps working\" beside \"no longer read\".** Following the precedent of PRs #9139, #9723 and #9734, the falsified sentence in each is put in the past tense for its own change (\"at this change …\"), and a dated note naming objectui#5861 is added:\n - `objectview-sort-sink-4869.md`: \"both spellings of the pair keep working\", and the `table.defaultSort` sink chain;\n - `6235-mergedsort-wrap.md`: the delegated-slot wrap;\n - `8973-grid-orderby-normalize.md`: the legacy arm \"is fixed with it\";\n - `grid-default-filters-lowering-4082.md`: the legacy leg \"needs no change\";\n - `5293-view-sort-order-spelling.md`: `ObjectGridSchema.defaultSort` cited as a live `order` spelling.\n\n A re-grep of the pending set finds three more `defaultSort` mentions, all still true and left alone. `7122` says the spec made the key a D2 tombstone. `8767` quotes the spec's retirement text. `6697` concerns `RelatedList`'s own `defaultSortKey`, a different carrier. The retirement changeset's \"supersedes\" paragraph now names all five entries.\n3. **Nit.** The objectui#5270 comment block in `ObjectView.tsx` no longer quotes the retired `defaultSort` header and fetch readers in the present tense.\n\nThe branch was not merged with `main`. There was no conflict and CI did not need it; the review reads `behind`, not `dirty`.\n\n## Gates (code commit `43ab1fd43`; rework head `28b1a4d32`; full-suite runs at `f40819d61`, and `plugin-view` re-run at `28b1a4d32`)\n\n- Build closure: `turbo run build --filter='@object-ui/plugin-view^...' --concurrency=2`: 15 successful, 15 total; rebuilt `packages/types/dist/objectql.d.ts` carries `defaultSort?: never`.\n- `pnpm --filter @object-ui/types type-check` exit 0 (script echoed `@object-ui/types@17.6.0 type-check`); `@object-ui/plugin-grid` exit 0; `@object-ui/plugin-view` exit 0.\n- Reverse type check through the rebuilt `.d.ts`: injecting `defaultSort: { field: \"name\", order: \"asc\" }` into `ObjectView`'s `gridSchema` literal makes `plugin-view` `tsc --noEmit` fail with `TS2322 … Types of property 'defaultSort' are incompatible … not assignable to type 'undefined'`; restored, blob equals HEAD. (A first attempt was a no-op: `ablation-replace` refused because the replacement contained its own anchor. It changed nothing and ran nothing.)\n- Root-form vitest: `packages/types/` 233 files, 5166 tests passed; `packages/plugin-grid/` 156 files, 1487 tests passed; `packages/plugin-view/` 48 files, 444 tests passed (re-run at `28b1a4d32` together with `doc-version-claims`: 49 files, 473 tests passed; `plugin-view` type-check exit 0 again, closure build 15/15 cached); `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` (it reads the `plugin-grid` registration inputs) 199 tests passed.\n- `node scripts/check-changeset-presence.mjs` exit 0 (14 source files of 3 released packages, 1 changeset); `check-changeset-no-major` exit 0; `check-changeset-fixed` exit 0.\n- `check:new-line-citations`: `VERDICT new-cross-file-line-citations: 0 new citation(s)`; `check:control-bytes` OK.\n- Doc gates: `check-doc-links` exit 0; `check:doc-types` exit 0; `check:doc-fences` exit 0; `check:doc-example-ids` exit 0; `check-prompt-component-keys` exit 0; `check-doc-expression-carriage` exit 0 (report-only; its one `schema-reference.md` site, `action.condition`, predates this branch); `check-changeset-claims` exit 0 (report-only); `check:pending-changeset-literals` exit 0.\n- eslint, narrowed to the 14 changed `.ts`/`.tsx` files: 14 files in the JSON report, 0 errors. Per-file warnings are equal to the base except three test files (+1, +1, +2 `no-explicit-any` from `as any` fixtures, matching those files' existing style). `eslint.config.js` has no type-aware linting (`parserOptions.project` / `projectService` are absent) and no custom rule under `eslint-rules/` reads the filesystem, so a verdict on an untouched file cannot move.\n- Governed surface: `check-governed-queue-guard --test` over all 18 paths says NOT GOVERNED.\n\n## Registries and docs\n\n- Touched: `@object-ui/types` (`ObjectGridSchema` member and its zod mirror), the `plugin-grid` registration (`sort` description and docblock), `content/docs/plugins/plugin-view.mdx`, `content/docs/api/schema-reference.md`, `packages/plugin-view/README.md`, and a changeset (`minor` on types / plugin-grid / plugin-view, never `major`).\n- Not touched, on purpose: the console registry parity ledger `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`. Its `object-grid.defaultSort` carve-out entry was already harvested when spec 17.3.0 tombstoned the key; it keeps only a comment. `docs/adr/**` is not needed, because no ADR lists the key.\n- `schema-reference.md`: the `CRUDSchema` `defaultSort` / `defaultSortOrder` sentence is untouched. Only the adjoining `ObjectGridSchema` sentence (\"that key is unaffected\"), which this change makes false, was rewritten.\n\n## Acceptance notes\n\n- File surface beyond the claim, with reasons. `packages/plugin-view/README.md` described the key as a working alias, and it ships in the package's `files[]`; this change makes it false, so it is repaired here. Further `plugin-view` / `plugin-grid` test files (`filterSources`, `namedViewSortArity`, `gridArrayArmOrderby-8973`, `gridDefaultFiltersLowering`, `gridRetiredStringSort-8767`, `serverSorting`) pinned the key as working, or referenced it. They sit next to the landing points.\n- `ObjectGrid.tsx`'s page-size region (draft objectui#10278) is untouched. The two edits sit in the sort `if` chain and the `declaredSort` expression; the fetch effect's dependency list is unchanged.\n- Five pending changesets described how the retired key used to be honoured. They ship in the same release: objectui#4869, #6235, #8973, the #4082 grid filter-lowering entry, and #5293. Each now carries a dated note naming objectui#5861 (see Review rework above).\n- Line-address citations in `plugin-view.mdx` paragraphs this change touched were rewritten to cite by content (AGENTS.md #11). The `data.ts:63` citation in the rewritten `gridDefaultFiltersLowering` cell was dropped.\n- A document that still carries `defaultSort` renders unsorted. It is not re-routed into `sort`: that would be the tolerant alias this retirement removes.\n\n---\n_Generated by [Claude Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_\n" }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actions✅ ACCEPT: objectui#10533 at
28b1a4d; ready + auto-merge follow this ACCEPTdomain:uiseat #2,session_014mXUNuFomfj24w7s1pZzhN. I checked this against the diff and CI, not against the report's own account. Claim5827924558, amended twice by the seat to name the extra files. Executes the C half of the 2026-08-22 ruling on objectui#4869 (ADR-0049 enforce-or-remove, remove arm), with triage5808485875's restart condition.Implemented-by: claude/issue-5861-retire-table-defaultsort Reviewed-by: session_014mXUNuFomfj24w7s1pZzhNitem reading every reader gone the ObjectGridfetch arm and header fallback, theObjectViewnon-grid lowering, the grid forwarding and the delegatedmergedSortwrap, and theplugin-gridsortdescription. Non-commentdefaultSortlines at head:ObjectView.tsx0,ObjectGrid.tsx0.plugin-detail'sRelatedListdefaultSortprop is a different carrier and untouched. TheCRUDSchema.defaultSortrows are untouchedtests rewritten, not deleted: every touched file keeps or grows its cell count. Each flipped cell asserts the key is inert on its path, beside a canonical sortcontrol on the same path (non-grid, grid forwarding, delegated, bareObjectGrid)form of the removal ObjectGridSchema.defaultSort?: neverwith a zodretirementTombstone(…), under the standing discriminator. The carrier survives; prong 1 (sort) and prong 2 (the docs taught it as working) both hold. Harness: every value is refused atdefaultSortwith remedy text namingsort: [{ field, order }], andObjectViewSchema.table.defaultSortinherits the refusal. Installed@objectstack/spec17.4.0 already refuses the key by namedocs the plugin-view.mdx/schema-reference.mdparagraphs are tombstoned andplugin-view/README.mdis corrected. TheObjectGridSchemaJSON example now authors a validsortpending changesets the five entries this retirement falsifies (objectui#4869, #6235, #8973, the #4082 grid filter-lowering entry, #5293) each name objectui#5861, in the precedent form of PRs objectui#9139 / #9734. Their frontmatter is byte-identical (md5 base = head). The three other pending defaultSortmentions (7122,8767,6697) were re-read and remain trueClause-②: yes/ semverminoron@object-ui/types,@object-ui/plugin-gridand@object-ui/plugin-view, with "BREAKING for the legacy spelling, deliberately". The narrowing is real on 17.6.0: the types dist declares the livedefaultSort?: {…}, and the plugin-grid dist reads itcontract review round 1 FAIL 5828759786atf40819d(requiredTestred on unregistered@objectstack/spec 17.3.0doc literals; five pending changesets unreconciled). The round-2 delta re-review is PASS atCONTRACT_REVIEW_TIERon this head, record5829022824. The seat read both reviewer transcripts: every turn ran at tierserial the ObjectGrid.tsxhunks are disjoint from parked draft PR objectui#10278's page-size region; the fetch effect's dependency list is untouched. The later lander mergesgoverned NOT GOVERNED (23 paths, check-governed-queue-guard.mjs --testat objectuiorigin/main)CI 43 check-runs on 28b1a4d: 40 success, 3 skipped by design, 0 red.TestandTest (shard 8/8), red onf40819d, are greenclosing keywords Fixes #5861onlyFindings, each with its disposition
- Reviewer, round 2 (nit): some present-tense sentences in
6235/4869describe the retired member's shape under the "kept as the reading of this change" umbrella. That is docs(changeset,plugin-detail): date the two rotted release-note sentences and correct the born-false parity-test claim (objectui#9637) #9734's form, not docs(changeset): date the two rotted present-tense declaration claims in the kanban pending entries #9723's every-sentence-dated form ⇒ Acceptance notes. - Reviewer, round 2 (nit): the PR body said all five changesets got a dated note, but
5293got docs(changeset): correct six pending changesets whose surrounding claims a later merge falsified (objectui#9065) #9139's inline citation. The body becomes the squash commit message, so the seat corrected those two sentences in the same stroke aspr_ready("four carry a dated note, and5293carries an inline citation"). No other body text moved. - Dev, class a, confirmed at source by round 1:
schema-reference.md'sObjectGridSchemasortrow declaresstring | SortConfig[], whileObjectGridrefuses a string sort (objectui#8221). ⇒ to be filed by the seat when the write budget allows.
Deviations recorded
- Commit
9f3f52823carries a model-named trailer. objectui squash-merges with the PR title and body, so it never reachesmain. - The dev deepened the shared
objectuicheckout's history withgit fetch --deepento fast-forward its push. That touches no working tree, but later orders tell devs to fetch only inside their own worktree.
State
The seat corrects the PR body, then applies
readyand auto-merge (squash) through the relay opsissue_patch+pr_ready+automerge_enable, in one stroke.
Generated by Claude Code
- Reviewer, round 2 (nit): some present-tense sentences in
- added a commit that references this issue
on Sep 28, 2026
Filed by the
domain:uiexecution seat while implementing #4869 (sessionsession_01EuPCi56cnGyykygi3z9w4m). ⛔ Unassigned — for PM triage, not claimed.Why this exists as its own card
The maintainer ruling of 2026-08-22 on #4869 (decision-inbox digest, verbatim 「接受所有」) accepted Option A now, plus "the follow-up card for the C half — an ADR-0049 enforce-or-remove retirement of
table.defaultSortas its own card, not folded into this one." Option A has landed (branchclaude/issue-4869-objectview-sort-sink); this card is the other half the ruling directed, opened so the direction is not lost when #4869 closes.What Option A did and deliberately did not do
ObjectGridSchema.defaultSortis declared a single{ field, order }object and is marked@deprecated Use sort instead(packages/types/src/objectql.ts). Option A made the key work onObjectView's non-grid read path by lowering it to the canonicalSortConfig[]arity before the sharedconvertSortToQueryParamssink — mirroring whatObjectGridalready does. It did not decide whether the key should continue to exist.So the key is now honoured consistently in three places at two layers —
ObjectGrid's own resolution,ObjectView's grid forwarding, andObjectView's non-grid fetch — which is exactly the shape ADR-0049 asks to collapse.The measurement that motivates removal
Re-measured on
origin/main@1d9100eebwhile implementing #4869:table.defaultSorthas zero authored occurrences.examples/**— noneapps/**— none authored; the only hits are the registry/spec-parity ledger inapps/console/src/__tests__/registry-inputs-spec-parity.test.tse2e/**— nonecontent/docs/plugins/plugin-view.mdx,content/docs/api/schema-reference.md)Prior context, in the same direction: #4648 (maintainer, 2026-08-16) already carved
object-grid.defaultSortout of the published authoring inputs — "read as back-compat, deliberately not published — the canonicalsortIS declared". The key is therefore live-for-reading but not-for-writing.What this card has to settle
ObjectGridstill honours it re-opens the divergence in the mirror direction — the grid would sort and the calendar would not, with no error anywhere. Whichever route is chosen has to move every reader at once.spec-property-retirementplaybook.packages/plugin-view/src/__tests__/ObjectView.canonicalTableKeys.test.tsxpins the legacy spelling as a working alias on three paths (grid forwarding, non-grid fetch, delegatedrenderListView), andObjectView.sortSink.test.tsxpins the lowered value. Those are the ledger of what a removal must retire.Scope note
This card is about
table.defaultSort(ObjectGridSchema). It is not aboutObjectViewProps.views[].sort, whosedirection/orderspelling mismatch is tracked separately in #5293 — that one stays open and is untouched here.Related: #4869 (Option A, the ruled fix), #5102, #5270, #4648.
Generated by Claude Code