refactor(components): bind SortDirection to @objectstack/spec (objectui#7265 components slice) - #9524
Merged
Conversation
…ui#7265) The DataTable renderer declared `SortDirection` module-locally, under the exact name `@objectstack/spec/shared` exports — the planted-premise class that `check:spec-symbols` exists to stop. The third member it carried, `null`, made the route question live: measured at the resolved pin @objectstack/spec@17.4.0, the spec's `SortDirection` is the two-member direction and nothing else. Reading the site settled it toward BIND rather than RENAME. `null` is not a third direction, it is the absence of one: written at exactly one place (the end of the client-side header cycle in `handleSort`) and read only by guards that guard `sortColumn` in the same breath — and `sortColumn`, the other half of the same state pair, already spelled its own empty case as `useState<string | null>` at the slot rather than folding it into a type name. So the strongest BIND form applied: the type is imported outright, the local declaration is gone rather than derived, and the third state is confined to the one state slot that carries it with the reason written there. `applySort` held a second inline hand copy of the same two members and now takes the bound type. Type-only: an `import type`, a deleted alias and an erased `useState` type argument. The declaration was never exported, so nothing published moves. Ledger regenerated with `--ledger`; the block shrank by this package's group. Ratchets: the spec-side properties the binding rests on are appended to this package's existing spec-symbol parity file, and the site plus the block are pinned in scripts/__tests__/spec-symbol-ledger-components-7265.test.ts. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of objectui#7265 — the
@object-ui/componentsslice of thecheck:spec-symbolsDEBT burn-down. Two groups remain (@object-ui/data-objectstack/normalizeFilterOperator,@object-ui/plugin-detail/RecordAlertProps), so the card must stay open andPart ofis used deliberately.The site, and what it really was
packages/components/src/renderers/complex/data-table.tsxdeclared, module-local:Measured at the RESOLVED pin —
@objectstack/spec@17.4.0, the versionpnpm-lock.yamlresolves andnode_modulesinstalls, not the 17.2.0 the seeding card asserted against — the spec'sSortDirectionisz.inputofSortDirectionEnumon the@objectstack/spec/sharedsubpath, and its members areascanddesc. Nothing else. So the third state was real and the RENAME question was genuinely live.Route: BIND, and the third state decided it — the other way
nullis not a third direction; it is the absence of one. Three readings at the site settled it:handleSort— and read only by guards (if (!sortColumn || !sortDirection), and theactiveSortternary) that guardsortColumnin the same breath.sortColumn, the other half of the same state pair, already spelled its own empty case at the slot (a useState of string-or-null) instead of folding it into a type name. Folding this half into a type name was the odd one out, not the house spelling.TableSortItem, whoseorderis the same two-member vocabulary. Those members must track the protocol; a RENAME would have left them hand-written under a name rule 1 can no longer match — the objectui#4592 hole the gate's own header records.So the strongest BIND form was available and is what landed: the type is imported outright, the local declaration is gone rather than re-derived, and the divergence is confined to the one state slot that carries it, with the reason written there.
applySortheld a second inline hand copy of the same two members in its signature and now takes the bound type too.ALLOW gained nothing: there is nothing here that deliberately differs from the spec's direction vocabulary. That absence is itself pinned, so a later hand that waives it instead has to delete an assertion.
The ledger, before and after
Both readings are the gate's own banner, on this branch:
The block was regenerated with
node scripts/check-spec-symbol-derivation.mjs --ledger, never hand-edited. Rule 2's ledger is untouched (18 unbacked claims in 5 packageson both readings) — the declaration this slice deleted carried no spec-alignment claim.An intermediate reading, taken deliberately, shows the site edit is real and not a ledger edit wearing its clothes — site fixed, block not yet regenerated:
Ratchets, with both readings from real runs
scripts/__tests__/spec-symbol-ledger-components-7265.test.ts(new) — the site half runs the gate's ownscanFileover the real file; the block half asserts shrink-only. Sibling of the core / app-shell / types ledger tests.Ablation, on the committed tree: the site and the gate script reverted to the pre-slice blobs, the new test run unchanged.
Restored from
HEADand verified by blob hash, not by an exit code: both paths back tod992df3/6e76953,git diff HEADempty.After restore, on the slice:
Test Files 3 passed (3) · Tests 91 passed (91)for the scripts-side ledger suites, andTest Files 7 passed (7) · Tests 200 passed (200)across everyscripts/__tests__suite that names the edited gate.The scanner is only evidence if it can fail, so the site assertion is paired with both controls: a throwaway fixture carrying the exact deleted declaration, which it MUST flag, and the derived union-extension spelling, which it must NOT — the second one also records what the gate does and does not decide. The gate is indifferent between the outright import and a derivation; the site chose, not the gate.
packages/components/src/__tests__/share-filter-sort-spec-parity.test.ts(appended, not duplicated — this package already owns a spec-symbol parity file from batch 5). The old declaration is gone, so there is no local name left to reach for; what is pinned is the SPEC-side property the binding rests on, the way the app-shell slice pinned its own module-local names. Its batch-5 census is left alone and the arrival is appended under its own card.Two ablations prove those lines are compiled rather than commentary, and that the confinement is load-bearing:
Note what (a)'s message contains: the parameter type it names IS the spec's two-member union, which is the binding having resolved. Both legs restored and verified by blob hash.
The behaviour the
| nullexists for is already pinned and is cited by name rather than re-tested:leaves client-side sorting exactly as it wasandnever asks for "no sort" — the third click returns to ascending, both indata-table-manual-sorting.test.tsx.Clause-②: still
no, and measured rather than asserted@object-ui/componentsis a published package, so the export surface was measured, not reasoned about. Three builds of the package: HEAD, HEAD again (the same-source control), and HEAD with the site reverted.SortDirectionappears in zero emitted files on both sides — the declaration was never exported, so nothing published moves and nothing an exported type accepts moves.Worth recording as a departure from the dispatch's expectation: it warned that a raw
distdiff is not an instrument, measured onpackages/typeswhere two builds of byte-identical source differ in union-member and object-key order. The same-source control says that does not hold in@object-ui/components— this package's emit is byte-stable, run to run. The normalised comparison was taken anyway and agrees; both readings are above, so the raw zero is corroborated rather than trusted.That measurement is also what backs the empty-frontmatter changeset: the emitted JavaScript is byte-identical, so the declaration that this releases nothing is a reading, not a claim.
node scripts/check-changeset-presence.mjsaccepts it explicitly.Checks run on this branch
check:spec-symbols@object-ui/componentsfull suiteTest Files 275 passed (275)·Tests 2682 passed (2682)@object-ui/componentstype-checktsc --noEmitandtsc -p tsconfig.test.json)@object-ui/componentslintdata-table.tsxat 34 warnings before AND after, same file, same configcheck:control-bytes·check:new-line-citations·check:test-path-roots·check:changeset-claims·check:phantom-deps·check:self-import·check:unreferenced-sources·check:installed-pin-claimsscripts/check-changeset-presence.mjsscripts/check-governed-queue-guard.mjs --testcheck:spec-floorswas NOT measured here: it refuses to run without a whole-workspace build (no-artifact: Build the workspace before running this gate). That is a prerequisite not met, not a red gate, and nothing in this diff moves a version range. Left to CI. Type-aware linting is not enabled ineslint.config.js(noproject/projectService), so this diff cannot move the verdict on any file it does not touch — which is what makes the targeted eslint reading above a narrowing rather than a gap.Acceptance notes — seen, deliberately not addressed here
@object-ui/types'TableSortItem.orderis a hand-declared'asc' | 'desc'. That is a documented decision of that package (it takes no dependencies), not a defect, so it is left alone — but the new type pinTableSortItem['order']equals the spec direction is the tripwire on it, and it reds on the day the spec grows a member.CLAIM_DEBT_ISSUEin the edited gate carries a figure written by an earlier slice of this same card, and it has been overtaken by the slices since. Not repaired here: the repair is to delete the figure, which is a rewrite of another slice's paragraph, and re-deriving it to "correct" it is the move AGENTS.md 完善设计器的每一个细节 #9 forbids. Noted, not filed.Generated by Claude Code