Repository navigation
[Decision] objectui execution half of the merged responsive retirement (objectstack#11027) — deleting the two dead implementations does NOT unblock the spec bump, and the retirement's "no other authorable carrier" is false here #7580
Description
Opened by the domain:ui execution-seat PM (session session_01EMrWaQw3XS5DxTHxp4yRyC) out of #4773, which stays as the measurement record. The work has grown a half that card never contained, and its body plus ten comments are a measurement history a removal PR would bury.
⛔ Not dispatchable as filed — the second half below needs a maintainer's ruling.
Where this stands
objectstack#11027 is merged on objectstack main (35e94c96b). packages/spec/src/ui/responsive.zod.ts no longer declares ResponsiveConfigSchema/ResponsiveConfig, BreakpointName, BreakpointColumnMapSchema/BreakpointColumnMap or BreakpointOrderMapSchema/BreakpointOrderMap — a REMOVED — tombstone stands where they were, with RETIRED_DEFS_BY_MAJOR[18] and the protocol-18 conversion. Merged and unreleased, so the discovery point is a lockfile refresh on caret ranges, not a deliberate bump. objectstack's own merged changeset names #4773 as the objectui execution half.
Half 1 — ruled, mechanical, and NOT sufficient on its own
The two implementations objectstack measured at zero callers: useResponsiveConfig (@object-ui/mobile) and ResponsiveProtocol (@object-ui/core). Their deletion is ruled and uncontested.
⛔ Do not dispatch this half alone. Every existing comment on #4773 says "delete the two dead implementations", and a PR that does only that leaves the bump red. That is the whole point of what follows.
Half 2 — unruled, and it reaches LIVE code
Measured on objectui origin/main, verified independently of the report that surfaced it:
| site | what it imports | status |
|---|---|---|
packages/types/src/mobile.ts:19 |
BreakpointName from @objectstack/spec/ui |
re-exported at :32 into @object-ui/types |
packages/layout/src/ResponsiveGrid.tsx:11 |
BreakpointColumnMap, BreakpointOrderMap |
re-exported at :32 |
packages/mobile/src/__tests__/responsive-config-spec-parity.test.ts:53 |
ResponsiveConfigSchema |
a runtime value import, not type-only |
Neither of the first two is dead:
BreakpointNameis a chokepoint. Republished into@object-ui/typesand consumed by live code —packages/mobile/src/breakpoints.ts:9,12,22,54(BREAKPOINTS,BREAKPOINT_ORDER,getCurrentBreakpoint),ResponsiveContainer.tsx:10, and theResponsiveValue<T>generic itself (types/src/mobile.ts:35).ResponsiveGridis a registered SDUI component.packages/layout/src/index.ts:206—ComponentRegistry.register('responsive-grid', ResponsiveGrid, …), withlayout:responsive-gridandresponsive-gridboth incli/src/utils/known-schema-types.ts, and a test that renders it precisely to prove the key is registered (app-shell-not-a-component-key.test.tsx:166). Itscolumnsinput is authorable and reachesresolveColumnClasses(columns)on the render path.
⭐ The retirement's stated ground is measurably overbroad, and that is the decision
The tombstone on responsive.zod.ts gives this as the reason the four types left with the key:
ResponsiveConfigSchema,BreakpointColumnMapSchema,BreakpointOrderMapSchemaand theBreakpointNameenum had no other authorable carrier and left with the key
The census behind it measured page.components[].responsive and found it inert. That finding is sound. But "no other authorable carrier" is a claim about the whole surface, and objectui has one: responsive-grid's columns is authorable, typed by BreakpointColumnMap, and applied by a resolver on the render path.
The Tailwind-style
xs…2xllayout vocabulary returns if and when a renderer implements it — in one change, with the engine (the #4834 rule).
A renderer implements it. That is why this is a ruling and not a repair.
The options
| what it does | cost | |
|---|---|---|
| A — re-home in objectui | declare BreakpointName locally in packages/types, and the column/order maps locally in packages/layout |
types/src/mobile.ts carries the reason verbatim: "Bound to the spec rather than re-declared (objectstack#4115): a local union under a spec export's name is read by the next reader as the spec's own definition, so a copy that is correct today is a planted premise tomorrow." Whoever takes A owes that comment an answer, not a deletion. |
| B — widen the retirement into objectui | retire ResponsiveGrid's columns and the mobile breakpoint hooks too |
⛔ refuted by measurement. It deletes a registered component's authorable input and a resolver that runs on the render path, on no evidence that either is unused. |
| C — take it back to the spec seat | ask whether the column vocabulary should survive, since the retirement's own return-condition appears already met | a cross-repo round trip on a merged, maintainer-ruled retirement — but it is the option the tombstone's own text points at |
⛔ This seat is not ruling between A and C. A reverses a recorded decision about published surface; C questions the stated ground of a merged retirement. Either is a maintainer's call. B is off the table on measurement, not on preference.
BreakpointOrderMap may be a third case: its own comment in ResponsiveGrid.tsx says it has no read point even there, so it may simply retire rather than be re-homed. That is not the same question as columns, and answering them together would hide it.
What a dispatchable brief needs, once ruled
Both halves in one change, because half 1 alone leaves the bump red. Plus the third site (responsive-config-spec-parity.test.ts:53) — a runtime value import, so it fails differently from the type-only ones and needs its own disposition.
Provenance
The live-code half was found by the os-dev seat working #4773 under a brief that told it to stop and report if the premise had changed. It did, and it was right to; every measurement above was re-verified here against origin/main and objectstack main before this card was written. Related: #4773 (the measurement record) · objectstack#11027 (the merged retirement) · objectstack#11029 (the cloud reading) · #7519 (owns the surviving MobileResponsiveConfig third).
Activity
os-warren commentedon Sep 4, 2026
Maintainer ruling recorded — A: re-home the breakpoint vocabulary in objectui;
BreakpointOrderMapretires; both halves in one PR.needs-user-decision→pm:queuein the same stroke.Director seat, summon 14 (
session_01LsEjuNMPitCHwEfYftZ1um, GitHubos-warren). Provenance: maintainer, live PM chat, 2026-09-04, decision batch #26, verbatim 「2 · objectui#7580 同意」 on the presented recommendation A (fallback C; B refused on measurement). Readings this ruling rests on were re-verified on objectuiorigin/maine30ed15and objectstackorigin/mainebb0822at 2026-09-04T03:49Z.The ruling
BreakpointNameis declared locally in@object-ui/types(packages/types/src/mobile.ts, where the spec import at line 19 and the re-export at line 32 stand today) — the renderer-side layout vocabulary lives with every other component schema. Its live readers stay as they are:packages/mobile(breakpoints.ts,useBreakpoint.ts,ResponsiveContainer.tsx,useResponsiveConfig.ts) andResponsiveValue<T>.BreakpointColumnMapis declared locally inpackages/layout(ResponsiveGrid.tsx, replacing the spec import at line 11);responsive-grid's authorablecolumnskeeps its type and its resolver.BreakpointOrderMapretires —ResponsiveGrid.tsx:26itself records it has no read point; the re-export at:32goes with it.useResponsiveConfigin@object-ui/mobile,ResponsiveProtocolin@object-ui/core, both measured at zero callers on [finding] 响应式词汇的两个零消费表面:useResponsiveConfig 生产零调用者(page.components[].responsive 因此实际未生效)、MobileComponentConfig 零消费者 #4773) are deleted, andpackages/mobile/src/__tests__/responsive-config-spec-parity.test.tsis deleted with them (its subjectResponsiveConfigSchemano longer exists in the spec;MobileResponsiveConfigstays finding(types): retiringMobileComponentConfigleavesMobileResponsiveConfigandGestureConfigas zero-consumer published types #7519's).mobile.ts:28is answered, not deleted: the spec no longer exports the name, so a local declaration is the only definition and cannot be mistaken for the spec's; the comment is rewritten to say exactly that and to cite objectstack#11027.⛔ Not C: putting a renderer-only vocabulary back into
@objectstack/specwhere the engine has no reader is the declared-but-unenforced shape ADR-0049 removes, and it would reverse a merged, ruled retirement. ⛔ Not B: deleting a registered component's authorable input and four runtime readers on no evidence. Whetherresponsive-grid.columnsis worth keeping at all (in-repo authoring: zero outsidecontent/docs/guide/layout.md) is a separate liveness question — afindingfor triage, ⛔ not folded here.Execution constraints, for the
domain:uiseatPart of/Fixesper the seat's judgement; [finding] 响应式词汇的两个零消费表面:useResponsiveConfig 生产零调用者(page.components[].responsive 因此实际未生效)、MobileComponentConfig 零消费者 #4773 stays the measurement record and closes with it.yes—@object-ui/typesgains exported symbols (mechanical floor); contract-review tier;needs:contract-reviewon card and PR when the diff exists. Changeset:@object-ui/typesand@object-ui/layoutper their policies;@object-ui/mobile/@object-ui/corefor the deletions.@objectstack/specpin bump must carryBlocked-by:this card — the retirement is merged upstream and unreleased (spec at 17.2.0; tombstone citesRETIRED_DEFS_BY_MAJOR[18]), so this PR must land before that bump.MobileComponentConfigleavesMobileResponsiveConfigandGestureConfigas zero-consumer published types #7519 (MobileResponsiveConfig), any open PR onpackages/types/src/mobile.tsorpackages/layout/src/ResponsiveGrid.tsx.BreakpointNamefrom@object-ui/typeswas not measured; the dev runs the consumer census (with a positive control) before deleting anything.Ruling authority: maintainer 2026-09-04 (batch #26). Related: #4773 · objectstack#11027 (
35e94c96b) · #7519 · objectstack#4115.Generated by Claude Code
claude commentedon Sep 4, 2026
Dev claim (os-dev subagent, re-dispatch after the fable 429)
Session:
session_01KbJQ1y1J12nZxYzFWhP8Q3Branch:
claude/issue-7580-responsive-rehomeWorktree:
objectui-issue-7580Base: objectui
origin/main=00d3f09(moved from the PM's 04:38Z reading of77b3cf1; re-fetched,git rev-parse HEADverified)Model:
opus, per the tier amendment 5535740735 —Clause-②: yesis unchanged, draft PR only, landing is the PM's.Assignee is the PM's and stays
os-sam; this comment is the identity bit. Card body read via REST and verified complete (6378 bytes, structure closed through the Provenance section, no sanitizer truncation).Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 7580, "status": "done", "branch": "claude/issue-7580-responsive-rehome", "pr": "https://github.com/objectstack-ai/objectui/pull/7598", "premise_still_valid": true, "summary": "Card body read via REST and verified COMPLETE (6378 bytes, structure closed through Provenance, no sanitizer truncation) — reporting that either way as instructed. All four ruling work-items were still undone on the real base: origin/main had moved 77b3cf1 -> 00d3f09 -> (merged) 4bb5e10, so every site was re-read rather than inherited. Both halves land in one PR. Half 2: BreakpointName declared locally in @object-ui/types (mobile.ts), BreakpointColumnMap in @object-ui/layout (ResponsiveGrid.tsx), both verbatim from the retired declarations; BreakpointOrderMap retired (ruling item 3); the objectstack#4115 comment ANSWERED and rewritten citing objectstack#11027 (ruling item 5). Half 1: useResponsiveConfig, ResponsiveProtocol and responsive-config-spec-parity.test.ts deleted. THREE SITES OUTSIDE THE RULING'S LIST, each in the card's own defect class and each of which would have left the bump red or a comment false, all named in the PR body: (1) types/src/index.ts:1349 re-exported ResponsiveConfig and BreakpointName STRAIGHT FROM THE SPEC as SpecResponsiveConfig / SpecBreakpointName — a second spec-import site nobody had named; dead once its two readers went, so dropped; (2) spec-ui-schema-reexports.test.ts carried three responsive deny-list rows under a ratchet that fails on rows the spec has retired — removed ahead of the ratchet firing, exactly as objectui#5716 handled ThemeModeSchema; (3) complex.ts:1037 asserted the ResponsiveConfig shape 'is NOT gone... which useResponsiveConfig really does read', which this PR falsifies — corrected in place, as was one sentence in mobile.ts's MobileResponsiveConfig note. KEY MECHANISM, measured not assumed: spec 17.2.0 PRE-dates the retirement and still exports both names, so a local declaration collides and check:spec-symbols goes red — I probed this before designing anything and captured the exact failure text. Held by two SELF-EXPIRING ALLOW entries in scripts/check-spec-symbol-derivation.mjs; that guard's ratchet 3 fails an entry excusing nothing, so the pin bump is MECHANICALLY FORCED to delete them. This is the repo's own precedent, not an invention: objectui#5716/#5668 did exactly this for the three theme document types under the same option A. Serial constraint honoured: PR #7577 has NOT landed (re-checked at merge time) and its file surface stays disjoint. Assignee untouched (os-sam). Draft, label not stripped, no ready flip, no enqueue, pin bump deliberately not done.", "tests": "All figures measured on the pushed head 2b63279 (after the final origin/main merge), quoting each gate's own verdict line; exit codes captured by redirect-then-read, never through a pipe. BUILD: pnpm --filter {types,core,layout,mobile} build green, plus the '^...' dependency-closure build first; rebuilt dist verified to actually carry the change (packages/types/dist/mobile.d.ts:63 'export type BreakpointName = ...', packages/layout/dist/ResponsiveGrid.d.ts:43 'export interface BreakpointColumnMap', and BreakpointOrderMap absent from layout/dist/index.d.ts) — so the type-checks below read a rebuilt .d.ts, not a cached one. TYPE-CHECK: all four green, script names echoed in the output ('type-check$ tsc --noEmit && tsc -p tsconfig.test.json'), so not a zero-match silent pass. VITEST (repo root, per this repo's guard): 238 files / 4255 tests, all passed. The edited gate script's OWN suites run separately from the derived families: scripts/__tests__/check-spec-symbol-derivation.test.ts + check-doc-component-types.test.ts = 2 files / 93 tests green. GATES: check:spec-symbols '1343 files scanned against 4959 spec export names; 20 declared dialects, 14 untriaged collisions' — 18 dialects before, so +2 is exactly my two entries and the untriaged count is unchanged (nothing pushed into DEBT); check:control-bytes 'OK (6223 tracked text files)'; check:doc-types and check:dist-completeness green. ESLINT on the 9 changed lintable files: 0 errors, 8 warnings, all no-explicit-any in complex.ts and IDENTICAL at baseline HEAD (verified by linting the HEAD blob through stdin) — my complex.ts change is comment-only, proved by diffing added non-comment lines (zero). NOTE the first eslint run showed 3 errors purely because I passed --no-inline-config, which disables the files' own sanctioned eslint-disable comments; re-run as CI runs it, clean. REVERSE VERIFICATION of the guard collision: mutation applied via perl -0pi, confirmed ON DISK before measuring (injected-line grep = 1, removed-import grep = 0, git hash-object differed from the HEAD blob), guard then failed with the exact quoted text; restored via 'git checkout HEAD -- packages/types/src/mobile.ts' inside a trap using an absolute repo root, and the restore PROVED by an empty 'git diff HEAD --stat' rather than by an exit code. DECLARED NARROWINGS, so they are not read as coverage: (a) check:readme-exports is NOT MEASURED locally rather than red — all 317 findings are the single prerequisite class 'type entry ./dist/index.d.ts is not on disk -- run pnpm build first' from packages outside my build closure, and ZERO name types/layout/mobile/core; the specific risk this diff creates (a README documenting a removed export) was measured directly instead and is zero across all 43 tracked READMEs. (b) Repo-wide pnpm lint and 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 — with ZERO live code references remaining (every survivor is a comment written here explaining the removal) against a BaseSchema control lit at 852. Type-aware linting is not enabled in eslint.config.js (no projectService/project/parserOptions), so this diff cannot move the verdict on any untouched file.", "mcp_calls": "1 — a single targeted search_code for the cross-repo consumer census; every other GitHub read and write went through the repo-scoped REST channel, which I probed open at the start (HTTP 200).", "open_questions": [ { "question": "The hotcrm / cloud consumer census the ruling carried forward is STILL UNMEASURED, and I can now name the boundary rather than just report a gap: GET /repos/objectstack-ai/cloud and .../hotcrm both return HTTP 403 for this session, against HTTP 200 for objectui and objectstack. 403 not 404 — the repos exist and this session is not authorized. Because GitHub code search only indexes what the caller can read, the org-wide search STRUCTURALLY cannot see them, so its silence about those two repos is not evidence of absence and I have not reported it as any. What I did measure, with positive controls: org-wide 'BreakpointName org:objectstack-ai' returns 14 hits (9 objectui, matching the in-repo census file-for-file, which is what makes the query trustworthy rather than empty; 5 objectstack, all the retirement's own sources), and the objectstack sibling checkout consumes zero of it with @object-ui/ imports lit at 124 files. This does NOT gate the re-home, which is a no-op for consumers (same name, members and export sites — only provenance moved). It bites on exactly one item: BreakpointOrderMap's removal from @object-ui/layout is a published-surface removal, in-repo readers zero, external readers unmeasurable from this seat.", "options": [ "A - accept as-is: the removal is ruling item 3, adjudicated, and the residual risk is one unread type; the changeset records it as a deliberate removal", "B - land the re-home now but defer only the BreakpointOrderMap deletion to a follow-up once someone with cloud/hotcrm access measures it", "C - grant this lane read access to cloud/hotcrm so the census can actually be run before landing" ], "recommendation": "A, with C as the durable fix. B buys almost nothing: BreakpointOrderMap has no read point even in the package that declared it, its own pre-existing comment says so, and it was published only because the retired ResponsiveConfigSchema paired it with the column map — so deferring it would keep a type alive whose entire reason for existing is already deleted upstream, and would split one ruled change across two PRs for a risk nobody can retire without C. C is worth raising with the maintainer independently of this card: this is the second time in one lane that a published-surface decision has had to be made on a census that a seat is structurally unable to run, and the 403 makes that permanent rather than incidental, not something a more careful dev can work around." } ], "out_of_scope_findings": [ "filed as #7599: skills/objectui/rules/protocol.md:289 attributes the six-key breakpoint vocabulary to the spec's BreakpointColumnMapSchema, which objectstack#11027 retires — true on today's pin, false at the bump. Deliberately NOT fixed here: skills/** is a governed published surface with its own line ratchet, nothing breaks mechanically, and the doc describes the `grid` renderer (a different component from responsive-grid, and one that does not import the type at all), so it is an attribution error only. Filed unassigned with `finding`, no pm:queue. Dedupe: scanned 76 open + 100 closed finding-labelled issues, no duplicate.", "NOT filed, reported instead because it is already owned: at the pin bump, page-nav-misc-spec-parity.test.ts:608 ['ResponsiveConfig','MobileResponsiveConfig'] WILL fire — it asserts the spec still owns ResponsiveConfig, which is why MobileResponsiveConfig carries its renamed spelling, and the retirement makes that false. That is the tripwire working as designed (it has fired twice before, each time handled in the bump PR), and the triage it triggers is #7519's, which the ruling explicitly keeps out of this card. Left untouched deliberately: removing the row early would destroy the signal #7519 needs. Recorded in the PR body's Sequencing section so the bump is not surprised by it." ], "pm_mechanism_assumptions": { "a_spec_derived_unions_goes_red": "FALSIFIED — and this is the one I was asked to test. The suite does NOT go red: @object-ui/types type-check is green with the re-home in place, and the suite is genuinely in that compilation set (tsc -p tsconfig.test.json --listFiles puts it among 560 files), so this is a measurement and not an unmeasured pass. Reason: the case imports BreakpointName from '../mobile' — never from the spec — and asserts a WIDTH ('xs'|...|'2xl' satisfies BreakpointName) that the re-home preserves exactly. So neither of the two dispositions offered was right as stated. 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. I restated it rather than deleting: the case keeps real enforcement (narrowing the now locally-owned union fails to compile instead of silently dropping a breakpoint), and the new comment records where the symbol goes next — at the bump it joins the suite's documented INVERSE population (names objectui uses that the spec does not have, asserted absent). That absence cannot be asserted yet because the pin still publishes the name, which is precisely what the self-expiring ALLOW entry covers.", "b_breakpointordermap_retires_cleanly": "CONFIRMED in-repo, with the external half unmeasurable (see open_questions). Zero references outside ResponsiveGrid.tsx and the CHANGELOGs; after the edit, zero live references anywhere, and it is absent from packages/layout/dist/index.d.ts.", "c_mobile_readers_need_no_change_beyond_import_source": "CONFIRMED, and stronger than stated — they needed NO change at all, not even to the import source. All four already imported BreakpointName from '@object-ui/types', not from the spec; only that package's internal provenance changed, so packages/mobile is untouched apart from deleting the dead hook's export line." } }Generated by Claude Code
hotlong commentedon Sep 4, 2026
Contract review on PR #7598: PASS at head
2b63279— director seat, summon 13, session_01WXyGTWPbbreqXow7Z2pZCk; the record is comment 5536189743 on the PR. Taken up because thedomain:uiseat parked the PR (5536055641) as complete work it cannot review at tier. The cloud / hotcrm consumer census the lane could not run is measured there: zero readers of every removed name in both repos onorigin/main, with positive controls — the dev's open question resolves as A (accept as-is).needs:contract-reviewcleared here and on the PR in this stroke (read-modify-write, read back), PR marked ready, auto-merge squash armed; this card closes with the merge andpm:dispatchedgoes with it.Generated by Claude Code
fix(types,layout,mobile,core): re-home the breakpoint vocabulary, del…