fix(mobile): SpecResponsiveConfig derives from its claim's source (#4598) - #4604
Merged
Merged
Conversation
) `useResponsiveConfig.ts` hand-declared an interface over the schema's four responsive keys, renamed off the schema's own symbol (`ResponsiveConfig` -> `SpecResponsiveConfig`), under a comment saying it mirrored `ResponsiveConfigSchema`. No import, no `z.infer`, no compile-time tie of any kind: the sentence was the whole connection. It agreed key-for-key on the day it was measured, which is the reason to fix it rather than the reason to leave it: the agreement was maintained by nobody and checked by nothing, while the comment told every later reader the copy was canonical. `ViewNavigationConfig` (#4588) read like this until it drifted on `mode`. Measured before choosing: `@object-ui/mobile` has no runtime dependency on `@objectstack/spec` (devDependency only), but `@object-ui/types` -- already its only runtime dependency -- publishes the schema's own type under this exact name, imported from `@objectstack/spec/ui`. So the type is re-exported through that existing edge; no new dependency, published name unchanged, and the entry `.d.ts` is byte-identical. A parity test pins the chain to `@objectstack/spec/ui` directly, because `@object-ui/types` re-growing a hand copy is the one link the re-export cannot see. The `CLAIM_DEBT` ledger entry is deleted -- ratchet #5 fails on it while it is stale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 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.
Fixes #4598
packages/mobile/src/useResponsiveConfig.ts:26hand-declared an interface over the schema's four responsive keys, renamed off the schema's own symbol (ResponsiveConfig→SpecResponsiveConfig), introduced by a comment saying it mirroredResponsiveConfigSchema. There was no import, noz.infer, and no other compile-time tie — the sentence was the entire connection.It agreed with the schema key-for-key on the day #4592's census measured it, and that is the reason to fix it rather than the reason to leave it: the agreement was maintained by nobody and checked by nothing, while the comment already told the next session the copy was canonical.
ViewNavigationConfig(#4588) read exactly like this until it had drifted onmode.The two measurements
(a) Does
@object-ui/mobiledirectly depend on@objectstack/spec? — No.packages/mobile/package.jsondependenciesis{ "@object-ui/types": "workspace:*" }and nothing else.@objectstack/specsits in devDependencies (^17.0.0-rc.6) — usable by a test, not by a published.d.ts.node scripts/check-phantom-dependencies.mjspasses on this branch, which is the arbiter confirming both halves.(b) Does a package mobile already depends on publish a spec-bound
ResponsiveConfig? — Yes.packages/types/src/index.ts:1223:Bound by import, not copied — verified at the source rather than inferred from the name. Two supporting measurements:
BreakpointNamethatuseResponsiveConfig.tsalready imported from@object-ui/typesis itself spec-bound:packages/types/src/mobile.ts:19imports it from@objectstack/spec/uiand re-exports it at:32. The file was already reaching the spec through this edge for one of its two types.@object-ui/core'sResolvedResponsiveConfig(packages/core/src/protocols/ResponsiveProtocol.ts:48), is not spec-bound — it is a local shape over a locally declaredBreakpointKeyunion (:27), so it was not the edge to use. ButResponsiveProtocol.ts:21consumesSpecResponsiveConfigfrom@object-ui/types— the sibling package already does exactly what this card asks mobile to do.Branch taken: 1 of the ruling's hierarchy
An existing dependency edge offers a spec-bound export, so the type is re-exported through it. No new dependency edge; branch 2 not needed, branch 3 (honest comment +
CLAIM_ALLOW) not reached, and the open question about adding@objectstack/specto mobile's dependencies does not arise.src/index.ts:24is unchanged, so the published name does not move.Red-first: the ratchet, verbatim
Predictions were written before the run (P1–P5). With the source fix applied and the ledger line still present,
node scripts/check-spec-symbol-derivation.mjsexited 1:After deleting
"@object-ui/mobile": ["SpecResponsiveConfig"]— the only edit toscripts/**, one line, zero insertions:Baseline on
origin/mainwas27 unbacked claims in 7 packages; the mobile key held exactly one symbol, so the key disappears. NoCLAIM_ALLOWentry was added — the claim is structurally backed, which was the ruling's test for branch 1.How the gate goes quiet, stated honestly
It stops reporting because after the fix there is no declaration to report:
scanFileForClaimscollects type aliases, interfaces, enums and variables, and a bareexport type { … }is none of those. That is the right outcome — the hand copy is gone — but the gate got there by seeing nothing, not by following the chain. It cannot: the binding runs mobile →@object-ui/types→@objectstack/spec/uiand the gate reads one package at a time. That is precisely why the test below is the load-bearing pin.The type pin, and why it is not vacuous
An
Equal< … >against@object-ui/types'SpecResponsiveConfigwould be vacuous — same import, true by construction. The newpackages/mobile/src/__tests__/responsive-config-spec-parity.test.tspins against@objectstack/spec/ui'sResponsiveConfigdirectly, which is falsifiable: it reds if@object-ui/typesever replaces its re-export with a hand copy of its own — drift one link up the chain, the only place this fix can still rot, and the one link no gate covers. Asatisfiesor one-wayextendscheck would be vacuous here for a different reason: it accepts a narrowing, so a copy that silently droppedorderwould still pass. Hence invariantEqual< A, B >. The spec import is legal because@objectstack/specis a devDependency and tests are not published.Reverse verification — re-grew the hand copy with
orderdropped (git checkout/file-copy, sha256-verified restore; nevergit stash).tsc -p tsconfig.test.json:Both type pins red, plus the runtime half's full-config literal, plus — unpredicted — the hook's own body, which reads
config.order. Restore verified:packages/mobile/src/useResponsiveConfig.ts: OKagainst the recorded sha256, and typecheck green again.Runtime must-not-change
useResponsiveConfig's body is untouched — this is a types-only change. There is no existing test suite foruseResponsiveConfig:packages/mobile/src/__tests__/held onlybreakpoints.test.ts,gesture-spec-parity.test.tsxanduseBreakpoint.test.ts. Saying that plainly rather than reporting a green suite that does not exist, and not inventing a behavior suite for a types-only change. The new file's runtime half covers the config surface against the schema value, which is what keeps the erased type assertions honest..d.tsanalysis — patchBuilt both ways with
dist/andtsconfig.tsbuildinfocleared:dist/index.d.ts— byte-identical,diffclean. The published entry surface does not move.dist/useResponsiveConfig.d.ts—export interface SpecResponsiveConfig { … }becomesexport type { SpecResponsiveConfig }re-exported from@object-ui/types. Same name, same position in the signatureuseResponsiveConfig(config?: SpecResponsiveConfig).Position analysis on the shape itself: a throwaway probe asserted
Equal< OldPublishedShape, SpecResponsiveConfig >whereOldPublishedShapeisorigin/main's interface copied verbatim — it compiled, so the two are invariant-equal, not merely mutually assignable. A pure re-binding with an identical published shape ⇒ patch. Not analyze (no surface change), never major.Verification
pnpm --workspace-concurrency=2 --filter "@object-ui/mobile^..." --filter "@object-ui/mobile" build— green (packages/types,packages/mobile).pnpm --filter "@object-ui/mobile" type-check(tsc --noEmit && tsc -p tsconfig.test.json) — exit 0.pnpm exec vitest run --maxWorkers=2 packages/mobile/src scripts/__tests__/check-spec-symbol-derivation.test.ts— 5 files, 49 tests passed. The gate's own suite is included because this PR edits its source of truth....@object-ui/mobile= dependents):turbo run type-check --filter="...@object-ui/mobile" --concurrency=2— 48 tasks successful, 48 total.check-control-bytes,check-phantom-dependencies,check-changeset-presence,check-changeset-no-major,check-changeset-fixed,check-type-check-coverage,check-lint-coverage,check-spec-symbol-derivation,check-doc-links.no-unused-varswarnings — the repo config ignores^_for arguments only, which is whypackages/types' equivalent parity file carries seven of them. Scoped aneslint-disable/enablepair around the pin block with the reason, rather than widening the shared config.grep -naPover every touched file including untracked) — clean.Scope
Four files:
packages/mobile/src/useResponsiveConfig.ts, the new test, the changeset, and the single ledger-line deletion inscripts/check-spec-symbol-derivation.mjs. Nothing else inscripts/**. #4597's surfaces (i18n spec-formatters,packages/types/src/views.ts, their ledger lines),packages/typesbase.ts, react, layout, app-shell, andcontent/docs/releases/**are untouched.Generated by Claude Code