fix(types,layout,mobile,core): re-home the breakpoint vocabulary, delete the two dead responsive implementations - #7598
Conversation
…ete the two dead responsive implementations (#7580) objectstack#11027 retired `@objectstack/spec`'s whole `ui/responsive` vocabulary on the ground that its four types "had no other authorable carrier". That ground is measurably false on the renderer side: `responsive-grid` is a REGISTERED SDUI component whose authorable `columns` is typed by `BreakpointColumnMap` and applied by `resolveColumnClasses` on the render path, and `BreakpointName` types four live readers in `@object-ui/mobile`. The tombstone's own return condition — the vocabulary "returns if and when a renderer implements it" — is already met here, so the two types a renderer reads are re-homed rather than retired. Both halves land together: deleting only the dead implementations would leave the next spec pin bump red, which is the card's central point. Re-homed, under the same names and members: - `BreakpointName` -> `@object-ui/types` (`mobile.ts`). No consumer change: same name, same six members, same export sites. Only provenance moved. - `BreakpointColumnMap` -> `@object-ui/layout` (`ResponsiveGrid.tsx`), verbatim from the retired `$strict` schema (no index signature restored). Removed: - `BreakpointOrderMap` — ruling item 3; no read point, published only because the retired `ResponsiveConfigSchema` paired it with the column map. - `useResponsiveConfig` (@object-ui/mobile) and `ResponsiveProtocol` (@object-ui/core), both measured at zero callers on #4773, with the parity test whose subject `ResponsiveConfigSchema` the retirement deletes. - `SpecResponsiveConfig` / `SpecBreakpointName` — dead re-exports once the two implementations went; dropped rather than re-declared, the disposition the retired i18n names in that file already carry. The objectstack#4115 comment is ANSWERED, not deleted (ruling item 5): the retirement leaves no spec definition for a local declaration to be mistaken for. The pin is still spec 17.2.0, which pre-dates the retirement and still exports both names, so the collision is real for one pin interval and is held by two SELF-EXPIRING entries in `scripts/check-spec-symbol-derivation.mjs` — ratchet 3 fails an ALLOW entry that excuses nothing, so the pin bump is forced to delete them. Same mechanism objectui#5716/#5668 used for the theme trio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ 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
|
Contract review: PASS at head
|
| name | cloud | hotcrm |
|---|---|---|
BreakpointName / BreakpointColumnMap / BreakpointOrderMap |
0 / 0 / 0 files | 0 / 0 / 0 files |
useResponsiveConfig, ResponsiveProtocol, SpecResponsiveConfig, SpecBreakpointName, ResolvedResponsiveState, BreakpointKey, BREAKPOINT_VALUES, resolveResponsiveConfig, getVisibilityClasses, getColumnClasses, getOrderClasses, shouldHideAtBreakpoint |
0 files | 0 files |
control: files importing @object-ui/ |
16 | 3 |
control: files importing @object-ui/core, mobile, layout or types |
0 | 2 |
So the one published-surface removal the census bore on (BreakpointOrderMap) has zero external readers in the two repos that were unreadable from the lane, and the dev's open question resolves as its own recommendation A: accept as-is. The access question the seat carried up (a lane that must decide published-surface removals cannot read two consumer repos) is real and is reported to the maintainer separately; it did not need to gate this PR.
Sequencing stands as the PR body records it: objectui's next @objectstack/spec pin bump deletes both ALLOW entries (forced), adds the absence pins, and expects the page-nav-misc-spec-parity tripwire on ResponsiveConfig to fire for #7519's triage. Landing this PR first is what makes that bump safe.
CI on 2b63279: 32 check runs, all completed, 0 failures (Lint, Type Check, Test shards 1–4, Build & E2E, all changeset gates green); mergeable_state clean.
Landing (this stroke): needs:contract-review cleared on this PR and on #7580 (read-modify-write, read back), PR marked ready, auto-merge squash armed — objectui lands only through the queue. The seat's pm:dispatched on #7580 clears with the auto-close.
Generated by Claude Code
Fixes #7580
Clause-②: yes
Implements the maintainer ruling of 2026-09-04 (batch #26, option A) recorded in comment 5535689433. All five numbered ruling items are adjudicated and were not re-litigated here. #4773 stays the measurement record and closes with this.
Fixesrather thanPart of, deliberately: both halves land here, so nothing on the card is left executable. The one thing the card names that is NOT here is the@objectstack/specpin bump, which the ruling explicitly places after this PR — see Sequencing below.Why re-home rather than retire with the key
objectstack#11027 retired the whole
ui/responsivevocabulary upstream on the stated ground that its four types "had no other authorable carrier". That is a claim about the whole surface, and it is measurably false on the renderer side:responsive-gridis a registered SDUI component (packages/layout/src/index.ts), itscolumnsinput is authorable, andresolveColumnClassesapplies it on the render path.BreakpointNametypes four live readers in@object-ui/mobile(breakpoints.ts,useBreakpoint.ts,ResponsiveContainer.tsx) plusResponsiveValue.The tombstone's own return condition — the vocabulary "returns if and when a renderer implements it" — is therefore already met on this side, which is why the two types a renderer reads are re-homed and only the unread ones retire.
What changed
Re-homed, surface-identical:
BreakpointNameis declared in@object-ui/types(mobile.ts) instead of re-exported from the spec. Same name, same six members, same export sites on@object-ui/typesand@object-ui/mobile— only its provenance moved, so no consumer sees a difference.BreakpointColumnMapis declared in@object-ui/layout(ResponsiveGrid.tsx), verbatim from the retired$strictschema: six optional column counts keyedxs…2xl, no index signature restored.Removed:
BreakpointOrderMap(ruling item 3) — no read point in the package; published only because the retiredResponsiveConfigSchemapaired it with the column map.useResponsiveConfig(@object-ui/mobile) andResponsiveProtocol(@object-ui/core) — both read the retiredResponsiveConfigSchema, both measured at zero callers on [finding] 响应式词汇的两个零消费表面:useResponsiveConfig 生产零调用者(page.components[].responsive 因此实际未生效)、MobileComponentConfig 零消费者 #4773 — withresponsive-config-spec-parity.test.ts, whose subject the retirement deletes.SpecResponsiveConfig/SpecBreakpointName(@object-ui/types) — dead re-exports once those two went. Dropped rather than re-declared locally, the same disposition the retired i18n names in that file already carry.The objectstack#4115 comment is answered, not deleted (ruling item 5). The reason it recorded was correct and is now spent: with the spec's declaration retired there is nothing left for a local one to be mistaken for. A planted premise needs something to be wrong about.
The mechanism that makes the sequencing self-enforcing
The pin is still
@objectstack/spec17.2.0, which pre-dates the retirement and still exports both names. So for one pin interval a local declaration and a live spec export share a name, andcheck:spec-symbolsfails on exactly that — measured, not assumed:This is held by two self-expiring entries in
scripts/check-spec-symbol-derivation.mjs. They cannot outlive the interval: that guard's ratchet 3 fails an ALLOW entry that excuses nothing, so the moment the pin moves past the retirement the entries stop matching and the bump is forced to delete them.This is not an invention — it is the repo's own precedent, followed step for step. objectui#5716 localized the three theme document types under the same option A, carried ALLOW entries "until the
@objectstack/spec17.2.0 refresh", and the entries "went stale exactly as their own comment predicted"; objectui#5668 deleted them and pinned the vacancy inpage-nav-misc-spec-parity.test.ts. Same shape, same disposition, one vocabulary over.For the same reason the three responsive rows in
spec-ui-schema-reexports.test.tsare removed here ahead of that suite's ratchet firing — precisely how theThemeModeSchemarow was handled at objectui#5716.Three sites outside the ruling's list
Each is in the card's own defect class (a consumption site of the retired vocabulary) and each would leave the bump red or a comment false. Naming them rather than folding them in silently:
packages/types/src/index.ts:1349re-exportedResponsiveConfig as SpecResponsiveConfigandBreakpointName as SpecBreakpointNamestraight from the spec — a second spec-import site the ruling does not name. Dead once its two readers went; dropped.packages/types/src/__tests__/spec-ui-schema-reexports.test.tscarried three responsive deny-list rows under a ratchet that fails on a row the spec has retired.packages/types/src/complex.ts:1037asserted that the sharedResponsiveConfigshape "is NOT gone — it stays live onpage.components[].responsive, whichuseResponsiveConfigreally does read". This PR falsifies both halves. Corrected in place rather than left standing, because a stale liveness claim is what the next agent reads as the measurement. The same applies to one sentence inmobile.ts'sMobileResponsiveConfignote, which cited theSpecResponsiveConfigre-export this PR drops; the rename still stands on its first ground, and the note now says so.The suite the brief flagged — the PM's reading is falsified
packages/types/src/__tests__/spec-derived-unions.test.tsnamesBreakpointNameand was expected to go red. Measured: it does not.@object-ui/typestype-checkis green with the re-home in place, and the suite is genuinely in that compilation set —tsc -p tsconfig.test.json --listFilesputs it among 560 files, so this is a measurement and not an unmeasured pass.The reason: that case imports
BreakpointNamefrom../mobile(never from the spec) and asserts a width —'xs' | … | '2xl' satisfies BreakpointName— which the re-home preserves exactly. What expired is not the assertion but the comment above it, which grouped the symbol under the objectstack#4115 "ledger burn-down" population: declarations proved equivalent to the spec's and then replaced by a binding. This one went the other way.So it is restated, not deleted. The case keeps its enforcement value — narrowing the now locally-owned union fails to compile rather than silently dropping a breakpoint — and the comment now records where the symbol goes next: at the pin bump it joins the suite's documented inverse population (names objectui uses that the spec does not have, each asserted absent). That absence cannot be asserted yet, because the pin still publishes the name. Deleting the case because its original reason expired would have removed live coverage on the exact declaration this ruling just made the repo responsible for.
The consumer census the ruling left open — still UNMEASURED, and here is the boundary
Whether hotcrm / cloud import
BreakpointNamefrom@object-ui/typeswas carried forward as an explicit confidence gap. It remains one, and the reason is now measured rather than assumed:GET /repos/objectstack-ai/objectuiGET /repos/objectstack-ai/objectstackGET /repos/objectstack-ai/cloudGET /repos/objectstack-ai/hotcrm403 rather than 404: the repositories exist, and this session is not authorized for them. Since GitHub code search only indexes what the caller can read, an org-wide search structurally cannot see them — its silence about those two repos is not evidence of absence, and is not reported as any.
What was measured, with positive controls:
BreakpointName org:objectstack-ai: 14 hits — 9 in objectui, 5 in objectstack (all the retirement's own spec sources). The 9 match the in-repo census file-for-file, which is what makes the query trustworthy rather than empty.BaseSchemalights 852 references against the same roots.@object-ui/types'BreakpointName, with@object-ui/imports lit at 124 files as the control.Why this does not gate the change: the re-home is a no-op for every consumer of
BreakpointName— identical name, members and export sites. The unmeasured census bites on exactly one item,BreakpointOrderMap, whose removal from@object-ui/layoutis a published-surface removal; that removal is ruling item 3, adjudicated, and in-repo readers are zero.Verification
Measured on the pushed head, after the final
origin/mainmerge.pnpm --filter {types,core,layout,mobile} run buildpnpm --filter {types,core,layout,mobile} run type-checkpnpm exec vitest run(four packages + the edited gate's own suite, repo root)pnpm check:spec-symbols✅ 1343 files scanned against 4959 spec export names; 20 declared dialects(18 before: +2, exactly the two entries added; untriaged collisions unchanged at 14)pnpm check:control-bytes✅ OK (6223 tracked text files)pnpm check:doc-types/check:dist-completenesseslinton the 9 changed lintable filesno-explicit-anyincomplex.ts, identical at baselineHEAD; the change there is comment-onlyBecause I edited a gate script, that script's own suite was run separately from the derived families:
scripts/__tests__/check-spec-symbol-derivation.test.tspluscheck-doc-component-types.test.ts(2 files, 93 tests, green).Declared narrowings, so they are not read as coverage:
check:readme-exportsis NOT MEASURED locally, not red. All 317 findings are the single prerequisite class "type entry./dist/index.d.tsis not on disk — runpnpm buildfirst", from packages outside my build closure; zero nametypes/layout/mobile/core. The specific risk this diff could create — a README documenting a removed export — was measured directly instead: an exhaustive census of every name this PR removes from a published surface, across all 43 tracked READMEs, returns zero.pnpm lintand the full 43-package downstream type-check were not run locally; CI runs the farm. In their place, the removal set was censused exhaustively by name (SpecResponsiveConfig,SpecBreakpointName,useResponsiveConfig,ResolvedResponsiveState,BreakpointOrderMap,BreakpointKey,BREAKPOINT_VALUES,ResolvedResponsiveConfig,resolveResponsiveConfig,getVisibilityClasses,getColumnClasses,getOrderClasses,shouldHideAtBreakpoint): zero live code references remain, every surviving mention being a comment written here explaining the removal, with theBaseSchemacontrol lit at 852.Sequencing — read this before the next pin bump
objectui's next
@objectstack/specpin bump must carryBlocked-by:#7580. The retirement is merged upstream and unreleased (spec pinned at 17.2.0; the tombstone citesRETIRED_DEFS_BY_MAJOR[18]), so this must land first. The pin bump is deliberately not done here.Three things that bump owes, two of them mechanically forced:
scripts/check-spec-symbol-derivation.mjs— forced, ratchet 3.BreakpointNameandBreakpointColumnMaptopage-nav-misc-spec-parity.test.ts, and move thespec-derived-unions.test.tscase into that suite's inverse population — the disposition objectui#5668 used for the theme trio.page-nav-misc-spec-parity.test.tsasserts the spec still ownsResponsiveConfig, which is whyMobileResponsiveConfigcarries its renamed spelling. The retirement makes that false, so the row fires and says "re-run the triage". That is the mechanism working, and the triage it triggers is finding(types): retiringMobileComponentConfigleavesMobileResponsiveConfigandGestureConfigas zero-consumer published types #7519's, not this card's — the ruling keepsMobileResponsiveConfigthere. Left untouched here deliberately: removing the row early would destroy the signal finding(types): retiringMobileComponentConfigleavesMobileResponsiveConfigandGestureConfigas zero-consumer published types #7519 needs.Review posture
Clause-②: yes—@object-ui/typesgains an exported symbol declaration.needs:contract-reviewis on both carriers (this PR and #7580). Draft, and staying draft: per the tier amendment (5535740735) the implementation ran atopusunder the quota-exhaustion exemption, which moves the implementation tier only — it does not move the contract-review floor and does not authorise landing. Landing is thedomain:uiseat's, after an in-seat contract review atclaude-fable-5-1.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code