Skip to content

feat(types,mobile)!: retire the MobileResponsiveConfig and GestureConfig published types - #7683

Merged
os-sam merged 2 commits into
mainfrom
claude/issue-7519-mobile-residue-retire
Sep 5, 2026
Merged

feat(types,mobile)!: retire the MobileResponsiveConfig and GestureConfig published types#7683
os-sam merged 2 commits into
mainfrom
claude/issue-7519-mobile-residue-retire

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #7519

Retires the MobileResponsiveConfig and GestureConfig published types via the ADR-0049 enforce-or-remove route, following what objectui#5942 (PR #7526) actually did for their only consumer, MobileComponentConfig. This narrows a published surface from two packages, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

Dispatched by the domain:ui PM seat at claude-fable-5-1 under the clause-② contract-review tier mandate; ruling on the card is comment 5548523458. Session session_01KbJQ1y1J12nZxYzFWhP8Q3.

Status: needs_decision, not done. The implementation of the route the ruling named (deletion, as #5942 did) is complete and verified below. A PM correction that arrived mid-task (the two-prong retire-vs-remove discriminator, objectui#7678) asked for a per-type prong measurement, and that measurement lands on a case the discriminator does not classify: both names carry CHANGELOG lines, but only rename-ledger rows and a reclaim note, never a behaviour promise. Which way that reads is recorded in the section "Two-prong discriminator, measured per type" and left to the docket rather than reconciled silently. Nothing in this PR is marked as settled that isn't.

Premise re-measured on origin/main at a472b071 before anything was deleted

The card was written before #5942 landed. What landed is the same shape the card predicted: the container is gone (PR #7526, commit 90665e07), and each of the two types is now a declaration plus two barrel re-exports and nothing else.

Table 1 — in-repo readers, per type: zero. Word-boundary git grep over every tracked file on origin/main (CHANGELOGs and the lockfile excluded), each zero paired with a control lit in the same run.

Name Code mentions What they are Controls (same query shape, same run)
MobileResponsiveConfig 3 mobile.ts declaration; types/src/index.ts and mobile/src/index.ts re-exports ResponsiveValue 11
GestureConfig 3 mobile.ts declaration; the same two barrel lines GestureType 36, GestureContext 7

Everything else the bare word hits is prose: doc comments (mobile.ts GestureType JSDoc, the Spec Touch Vocabulary block, useSpecGesture.ts JSDoc — which names the type only to say it is not the one it reads), the #5942 changeset, and rows/comments in page-nav-misc-spec-parity.test.ts. No annotation, extends, cast, generic argument, array or import of either name exists outside the two barrels. examples/, apps/, e2e/, skills/, docs/, content/, scripts/ and both package READMEs: 0 hits for both (control: useGesture 4 hits in the mobile README, 3 in the skills guide).

Sibling objectstack checkout (5c584231): MobileResponsiveConfig 0; GestureConfig 5 hits, every one about the spec's own retired GestureConfig(Schema) (release notes, upgrade guide, migration entries, the interaction-config-retirement.test.ts ledger) — none imports from @object-ui/*; no package.json in that repo depends on @object-ui/types or @object-ui/mobile at all (control: @objectstack/spec in 73 package.json files; ComponentSchema 270 hits).

Table 2 — published-surface reachability: both names ship from two packages. Measured on the built dist at the BASE commit (fresh worktree, so no stale dist could exist):

Emitted .d.ts occurrences (word-boundary) MobileResponsiveConfig GestureConfig control GestureContext
packages/types/dist 2 (barrel + declaration) 4 (barrel, declaration, 2 doc mentions) 2
packages/mobile/dist 1 (barrel) 2 (barrel + useSpecGesture.d.ts doc mention) 3

Both packages are public (17.6.0), exports map "." points at ./dist/index.d.ts, and both barrels re-export the names one hop from the root. Clause-② is yes on my own reading too: a TypeScript consumer of either name from either package breaks.

Route: deletion, because that is what #5942 did — and a tombstone has nowhere to live here

  • Zod mirror check first, as the ruling required. packages/types/src/zod/ has no mobile twin and its index never names either type; neither shape was ever parsed by any mirror. So the "deletion under a non-strict z.object is a silent strip, a tombstone is a named refusal" argument has no schema to apply to — there is no twin to host a retirementTombstone(), and the refusal can only be type-level. That is the objectui#7654 shape, and it is why the pin below is tsc-only rather than a shortcut.
  • Deletion, not ?: never, the route finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 and finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 took: whole interfaces go, so there is no surviving object to hang a never key on, and SpecGestureConfig is not a successor of GestureConfig (different contract: a tuning record with no action). Whether the two-prong discriminator licenses this for these two names is measured in the next section — it is the open question of this PR.
  • Both barrel re-exports go with the declarations. Each declaration is replaced by a // RETIRED note — a line comment, so declaration emit strips it — and the surviving JSDoc on GestureType and on useSpecGesture no longer names the retired symbol. That restores the property feat(types,mobile)!: retire the MobileComponentConfig published type #7526's contract-review round established: both names occur zero times across every emitted .d.ts after rebuild (grep -rhow NAME packages/*/dist --include='*.d.ts' = 0 / 0; control GestureContext 2 / 3, unchanged). The one RETIRED string that IS emitted from dist/mobile.d.ts is the pre-existing RETIRED_DEFS_BY_MAJOR[18] identifier in the BreakpointName JSDoc at BASE — a real reference, not a dangling pointer.
  • No behaviour retired. useResponsive / ResponsiveContainer / useBreakpoint and useGesture ({ type, onGesture, threshold?, longPressDuration?, enabled? }) are untouched; ResponsiveValue, BreakpointName, GestureType, GestureContext stay exported from both packages.

Two-prong discriminator, measured per type (PM correction, objectui#7678)

The discriminator, quoted from the #5941 / #7526 changesets: a tombstone exists to steer authors to a named live replacement KEY, or to keep loud a key the docs taught as working. mobile.ts's one-line form in the #4919 note (the card's mobile.ts:129; that note starts at line 119 at BASE and at line 122 after this diff — the note itself is untouched, its correction is #7678's) is not relied on.

Prong 1 — a named live replacement key: none, for either.

Type Measurement (git grep on origin/main, exit captured before any pipe)
MobileResponsiveConfig its distinctive keys stackOnMobile / showOnly / stackBreakpoint have 0 readers outside the declaration (control: columns 3686 lines, same shape); no mounted type has carried a responsive member since #5942. The behaviour lives in hooks (useResponsive / ResponsiveContainer / useBreakpoint), which are not keys.
GestureConfig no dispatcher reads a gesture action (0 hits); the only gestures key on any type is TouchInteraction.gestures: SpecGestureConfig[] — a different contract, and TouchInteraction itself has no reader outside its declaration and barrels. SpecGestureConfig is not a successor.

Prong 2 — advertised as working (release-note/CHANGELOG line, or a published JSDoc @default): CHANGELOG lines exist for both, and every one is a name-ledger entry. Same query shape (word-boundary over CHANGELOG.md + every packages/*/CHANGELOG.md, origin/main) applied to the two names, to the precedents, and to the controls:

Name CHANGELOG lines What they are Published @default / @example on the declaration
MobileResponsiveConfig 6 one rename-ledger row from the objectstack#4115 batch (ResponsiveConfigMobileResponsiveConfig · "mobile box config, not the spec's SDUI grid contract"), replicated into six package CHANGELOGs because the fixed group publishes one changeset text everywhere none (0)
GestureConfig 10 six of the same rename-ledger row (GestureConfigTouchGestureConfig · "gesture→action binding, not per-gesture tuning") + the objectui#3363 reclaim note in two packages ("Reclaim the natural names GestureType and GestureConfig" / "TouchGestureConfigGestureConfig — the flat gesture→action handler binding … Nothing about either shape changed") none (0)
control triggerIcon (tombstoned by #7654 on prong 2) 1 (CHANGELOG.md:578, 3.3.0 "New … triggerIcon … options") a feature announcement @default 'MessageCircle' (lit)
control striped (tombstoned, prong 1) 19
MobileComponentConfig (removed outright, #7526) 0
MobileOverrides / mobileOverrides (removed outright, #5941) 0 / 0

So the reading is not "neither prong, cleanly", which is the sentence the PM asked me to write only if it were true. It is: prong 1 no; prong 2 — CHANGELOG lines exist, all of them record a name (a rename, then a reclaim) and none records behaviour (no renderer or dispatcher named as reading the type, no @example, no @default), which is a case the two-prong wording does not classify, and one the precedents did not face (their counts were 0).

Two readings, two routes:

  • Reading A — a name-ledger line is not "taught as working". Prong 2 protects authors against silent loss: under a non-strict z.object a deleted key is stripped, and only a tombstone turns that into a named refusal. Nothing here can be silent — a whole unmirrored, unmounted type is never parsed, and its deletion is already the compiler's own loud refusal (TS2305 / TS2724, measured above). The rename rows and the reclaim note told readers the name, not that anything read it. Under A the licensed route is remove outright, exactly as implemented, and the same route the identical lineage (MobileComponentConfig) took.
  • Reading B — any release-note line counts. Then prong 2 fires for both and a tombstone is the licensed route. But the only tombstone shape available with no mirror and no surviving object is a type-level one this repo has never used: keep both names exported as never aliases with a JSDoc migration note (export type GestureConfig = never), from both barrels, indefinitely. That keeps the names on the published surface, gives a generic "not assignable to type never" at the use site (the note reaches only hover / .d.ts readers), and contradicts the finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 precedent for the same lineage. It is also a new tombstone pattern, which is a package-wide decision rather than this card's.

My recommendation is A — the measurement shows name-ledger lines and nothing that promised behaviour, and the hazard prong 2 exists for cannot occur here — but the PM's instruction was explicit that this is not mine to default, so the PR reports needs_decision and stays a draft. If B is chosen, this PR is reworked (the pin test's tsc leg inverts: the names must resolve to never), not patched.

The loud-signal requirement, measured with a real external consumer on both legs

A tiny consumer package (its own directory, node_modules/@object-ui/* symlinked to the worktree packages, moduleResolution: bundler) compiled one probe file per name per package against the built .d.ts through the real exports map; --traceResolution confirms both control files resolve to packages/types/dist/index.d.ts and packages/mobile/dist/index.d.ts. Each package was rebuilt before the after-leg reading.

Probe Before (dist at BASE) After (rebuilt at this head)
import type { MobileResponsiveConfig } from '@object-ui/types' exit 0, silent TS2305 no exported member
import type { GestureConfig } from '@object-ui/types' exit 0, silent TS2724Did you mean 'SpecGestureConfig'?
same two from @object-ui/mobile exit 0, silent TS2305 / TS2724 with the same suggestion
controls: GestureContext + GestureType from types; GestureContext + ResponsiveValue from mobile exit 0 exit 0

The before column is the card's defect reproduced: the dead names import cleanly from both published packages. The compiler's SpecGestureConfig suggestion is a lexical near-match, not a migration target, and the changeset says so.

The tripwire pins: retired with the types, and the absence pinned instead

page-nav-misc-spec-parity.test.ts carried three rows on these names (['ResponsiveConfig','MobileResponsiveConfig'], ['GestureConfig','TouchGestureConfig'], and the MobileResponsiveConfig "spec does not own" row). The card asked what happens to them. All three would have stayed green after the deletion — they assert facts about the spec's export set, not about this package — which is exactly the phantom-check shape: a pin on a name this package no longer exports guards nothing, because the spec re-publishing GestureConfig or ResponsiveConfig would collide with no export here. The rows are removed and the file's history header records why, the way it records each earlier tripwire firing; the GestureType row stays (live, read by two hooks).

The forward-looking guard is the new mobile-residue-retired-7519.test.ts:

  • tsc channel (instrument: tsc -p packages/types/tsconfig.test.json; program membership proven with --listFiles, which lists the file and reads src/mobile.ts / src/index.ts directly — 0 dist files in the program): four @ts-expect-error lines through the import('../index.js').NAME / import('../mobile.js').NAME spelling, each on a line whose only possible diagnostic is the missing export (the value is used, the literal would type-check cleanly if the declaration came back), and each paired with a live name reached through the identical spelling with no directive so a broken specifier turns the control red rather than satisfying the directive by TS2307.
  • Source-read channel (vitest): both barrels and the declaring file are read off disk (zero-deps package, same instrument as the OfflineConfig pin) and asserted to no longer carry the names while GestureContext / ResponsiveValue / GestureType still do, plus one // RETIRED note per name. This is the leg that reaches the @object-ui/mobile barrel, which the compiler leg cannot from this package.

Reverse verification (after commit; restore under an EXIT INT TERM trap with absolute paths)

Prediction written before the leg, then the three source files (mobile.ts and both barrels) were put back to their BASE content with git checkout BASE -- ABSOLUTE_PATHS. Mutation proven on disk by anchored counts (export interface MobileResponsiveConfig 0→1, export interface GestureConfig { 0→1, // RETIRED (objectui#7519 2→0, barrel lines 0→1 in both barrels) and blob movement (each file's git hash-object == its BASE blob and != its HEAD blob; an empty hash is coded as failure). No rebuild was needed on either leg: both instruments read src/, never dist/.

Leg Predicted Observed
P1 tsc -p tsconfig.test.json on the mutated tree non-zero; exactly 4 × TS2578 unused-directive on the four directive lines; 0 other diagnostics exit 2; 4 × TS2578 at lines 63, 65, 71, 73; 0 other — met
P2 vitest on the single file, mutated tree 3 failed (source-read describe) / 3 passed (tsc-channel describe passes at runtime because types are erased — the reason vitest is not the instrument for P1) 3 failed / 3 passed — met

Restore: git checkout HEAD -- ABSOLUTE_PATHS from the trap, then proven by state, not by exit code — git diff HEAD empty, git status --porcelain empty (index included), all three blobs equal their HEAD blobs, anchors back at 2 / 0 / 0 / 0 / 0.

Verification union — run at 45df4a2ec after the final commit, each verdict quoted from the gate's own output

  • pnpm exec vitest run --maxWorkers=2 packages/types/ packages/mobile/ (repo root, under the shared verify lock): Test Files 107 passed (107) · Tests 1757 passed (1757); the new file alone with --reporter=verbose (at 125643e5b, the commit that introduced it): 6 / 6, and page-nav-misc-spec-parity.test.ts still green. The second commit 45df4a2ec changes // comments and the changeset only; the union, the dist counts (0 / 0, control 5) and the consumer probe were re-run on it.
  • tsc -p packages/types/tsconfig.test.json --listFiles: exit 0, 0 diagnostics, new file listed (104 test files in the program).
  • pnpm --filter @object-ui/types type-check and pnpm --filter @object-ui/mobile type-check (the latter reads packages/types/dist, rebuilt first): exit 0 / 0.
  • pnpm --filter @object-ui/types build and pnpm --filter @object-ui/mobile build: ✓ dist completeness: 1 package(s) complete for each (120 / 48 emitted files).
  • pnpm --filter @object-ui/types lint / @object-ui/mobile lint: exit 0 / 0, no message on any touched file (pre-existing no-explicit-any warnings only, elsewhere).
  • check-changeset-presence (✅ 5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-overwrite (✅ No pre-existing changeset was modified or deleted).
  • check:control-bytes, check:spec-symbols, check:unreferenced-sources (OK Every shipped source file in every covered package is reachable), check:published-tsconfig-exclude (OK -- all 34 enforced package(s)), check:phantom-deps (✅ Every in-scope import is declared), check:side-effects-array: all exit 0.

NOT MEASURED locally, declared, left to CI: check:readme-exports collapsed on 34 unbuilt packages (the population COLLAPSED -- this run proves nothing; PRECONDITION NOT MET — it needs the full-repo build CI performs). The narrowing evidence: neither package README mentions either name (0 / 0, control useGesture 4). Likewise the full downstream type-check sweep of every @object-ui/types consumer (pnpm --filter '...@object-ui/types' type-check, prefix form = downstream) is CI's run; the whole-tree word-boundary grep above (controls lit) is why a zero there is expected, and the one in-repo package that re-exported the names, @object-ui/mobile, was type-checked against the rebuilt dist.

Scope fence, declared honestly

The fence was packages/types/src/mobile.ts and the two barrels. Three further files are touched, and each is inside this retirement's own blast radius rather than a cascade:

  • packages/mobile/src/useSpecGesture.tsJSDoc only. Its doc named GestureConfig to say "not this package's own GestureConfig"; that sentence is emitted into packages/mobile/dist/useSpecGesture.d.ts, so leaving it would ship a dangling reference to a removed name — the exact defect feat(types,mobile)!: retire the MobileComponentConfig published type #7526's contract-review round failed on. No in-flight branch touches this file (scan below).
  • packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts — the three rows the card itself asked about (disposition above).
  • packages/types/src/__tests__/mobile-residue-retired-7519.test.ts — new retirement pin.

⛔ Not touched: registry.ts, complex.ts, ChartDataSeriesSchema. No further zero-consumer types were found one level down: ResponsiveValue, GestureType, GestureContext are all read by hooks (counts above), so nothing to file.

In-flight overlap scan (every claude/* branch on the remote naming mobile / gesture / responsive / 7654): issue-7654-trigger-icon-retire touches only complex.ts + its own test and changeset; issue-6856-mobile-guide-useoffline-snippet is an empty probe branch at main; issue-4773-responsive-vocabulary-liveness is a stale pointer at an old main commit with no unique work. No file overlap with this PR.

Blast radius, stated plainly

The in-repo consumer count is zero, and that is "no consumers in this repo", not "no consumers". Both names shipped from two public packages for several releases; an out-of-repo consumer importing either from either package is invisible from here, which is why the changeset is graded minor on the published-surface change (this repo's version-alignment rule reserves major for following @objectstack across a major, the same classification #5942 and #4919 used) and carries the upgrade note, the hook pointers, and the warning against the compiler's SpecGestureConfig suggestion.

Clause-② status

  • needs:contract-review added to the card (additive endpoint, compare-read-back: union complete, nothing stripped) and to this PR.
  • Draft; not flipped ready; not enqueued.

Generated by Claude Code

…fig published types

Both were declared in packages/types/src/mobile.ts and published twice --
re-exported by the @object-ui/types root barrel and by @object-ui/mobile's --
and each had exactly one consumer: the `responsive` / `gestures` member of
MobileComponentConfig, which objectui#5942 (PR #7526) retired. Re-measured on
origin/main a472b07 before deleting: each name was a declaration plus two
barrel re-exports and nothing else (read-shape probes over all tracked files,
controls GestureType 36 / GestureContext 7 / ResponsiveValue 11 lit in the
same run; objectstack sibling checkout 0 hits for both, control ComponentSchema
270; no package.json in that repo depends on @object-ui/*).

Same route the container took: removed outright, not tombstoned. The whole
interface goes, so there is no surviving object to hang a `?: never` key on;
no documentation ever taught either; and there is no Zod mirror at all --
packages/types/src/zod/ has never had a mobile twin -- so the "deletion under a
non-strict z.object is a silent strip" argument has no schema to apply to and
the refusal is type-level only (TS2305 / TS2724 at the import), the
objectui#7654 shape.

Both barrels drop the names; a `//` RETIRED note replaces each declaration
(line comments are stripped from declaration emit, the objectui#5942
contract-review lesson), and the surviving JSDoc on GestureType and the
useSpecGesture hook no longer names the retired symbol, so both names occur
zero times across every emitted .d.ts (rebuilt and measured; control
GestureContext 2 / 3).

The page-nav-misc-spec-parity rows that pinned the two names' spec
non-ownership retire with the types: a name this package no longer exports
cannot collide with anything, so a pin on it would guard nothing. The absence
is pinned instead in mobile-residue-retired-7519.test.ts on the tsc channel
(`@ts-expect-error` through the import('...') spelling, each paired with a
live control through the same spelling) and by a source read of both barrels.

Changeset: minor for @object-ui/types and @object-ui/mobile, graded on the
published-surface change -- the in-repo consumer count is zero, consumers
outside this repository are not visible from here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
…ired type

The RETIRED notes and the changeset said the two-prong discriminator did
not apply; the PM's mid-task correction (objectui#7678) asks for the
measurement per key instead. Prong 1 (named live replacement key): none
for either -- MobileResponsiveConfig's distinctive keys have zero readers
outside the declaration, no dispatcher reads a gesture `action`, and
TouchInteraction.gestures is SpecGestureConfig[], a different contract.
Prong 2 (advertised as working): the only CHANGELOG lines are the
objectstack#4115 rename-ledger rows (6 / 6, replicated per package) and,
for GestureConfig, the objectui#3363 reclaim note (4 lines); none teaches
a renderer or dispatcher reading them and no member carried a published
@default. Controls in the same run: triggerIcon 1 (tombstoned on prong 2),
striped 19; MobileComponentConfig / MobileOverrides 0 (removed outright).
Whether a rename-ledger row or a reclaim note counts as "taught as
working" is recorded on objectui#7519 for the docket, not decided in the
comment. The objectui#4919 note (the one-line form under correction as
objectui#7678) is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Contract review — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 80/80 assistant turns stamped claude-fable-5-1, 0 fallback notices. Lit control: the same reader returns claude-opus-5 thousands of times across this session's transcripts, so the zero is a reading. Adopted verbatim — not edited, trimmed or paraphrased.


Contract review — PR #7683 (claude/issue-7519-mobile-residue-retire, head 45df4a2ec) for objectui#7519

Instruments and trees. Two detached scratch worktrees, removed after use: HEAD 45df4a2ec and its merge-base with origin/main, a472b0716 (the commit the author measured against; origin/main is a3eb5d07a, two commits ahead — #7660 and #7669 — neither touching any file this PR changes). Both @object-ui/types (120 emitted files) and @object-ui/mobile (48) built green on both sides. Sibling objectstack read at 5c584231. Neither primary checkout was modified (porcelain 0 / 0 at exit). Raw logs: /tmp/claude-0/-home-user/a10fe65b-83d8-5954-95c5-cffe775e6ac0/scratchpad/7683-*.

① Derived judgments

Each line is a published-surface or accept-set change at head, with the measurement that decides it. Consumer probes compile a separate package whose node_modules/@object-ui/* symlink to the built worktree packages (moduleResolution: bundler, skipLibCheck); --traceResolution shows @object-ui/typespackages/types/dist/index.d.ts@17.6.0 and @object-ui/mobilepackages/mobile/dist/index.d.ts@17.6.0 on both legs. Controls on both legs: GestureContext/GestureType from types and GestureContext/ResponsiveValue<number> from mobile exit 0; negative control const k: GestureType = 'not-a-gesture' gives TS2322 on both legs, so the instrument surfaces real type errors, not only resolution.

  1. @object-ui/types no longer exports MobileResponsiveConfigRIGHT. Before: exit 0, silent. After: TS2305 Module '"@object-ui/types"' has no exported member 'MobileResponsiveConfig'.
  2. @object-ui/types no longer exports GestureConfigRIGHT. Before: exit 0. After: TS2724 ... has no exported member named 'GestureConfig'. Did you mean 'SpecGestureConfig'?; the import('@object-ui/types').GestureConfig spelling gives TS2694.
  3. @object-ui/mobile no longer re-exports MobileResponsiveConfigRIGHT. Before exit 0; after TS2305 through packages/mobile/dist/index.d.ts.
  4. @object-ui/mobile no longer re-exports GestureConfigRIGHT. Before exit 0; after TS2724 with the same suggestion. The changeset's warning against that suggestion is measured true.
  5. Declarations removed from packages/types/src/mobile.ts, replaced by // notes that declaration emit strips — RIGHT. Word-boundary counts across every emitted .d.ts, base → head: MobileResponsiveConfig types 2→0, mobile 1→0; GestureConfig types 4→0, mobile 2→0; control GestureContext 2/3 → 2/3. Dangling-pointer strings in emitted .d.ts: see the RETIRED note 0/0, RETIRED (objectui#7519 0/0, objectui#7519 1→0 (the base's JSDoc pointer to the card left with the declaration it sat on). The feat(types,mobile)!: retire the MobileComponentConfig published type #7526 review's zero-occurrence property holds.
  6. JSDoc rewrites that are emitted — GestureType's doc in mobile.ts and useSpecGesture's doc in packages/mobile/src/useSpecGesture.ts no longer name GestureConfigRIGHT. These are the two "doc mentions" that made the base counts 4 and 2 rather than 2 and 1; without them the published .d.ts would carry a name that resolves to nothing, the exact defect feat(types,mobile)!: retire the MobileComponentConfig published type #7526's first round failed on.
  7. Runtime accept set — unchanged, RIGHT. packages/types/src/zod/ (19 files) names neither type nor mobile (0 hits); the emitted mobile.js carries only SPEC_GESTURE_TYPES. Nothing parsed either shape before or after, so there is no strip-vs-refuse question; the only channel these names ever had was the compiler.
  8. Three parity rows removed from page-nav-misc-spec-parity.test.tsRIGHT. Leg P5: retirement in place, base's parity file (blob 541b2fb213) restored over head's → 45/45 green. The rows were phantom checks after the retirement, as the PR claims. Installed @objectstack/spec is 17.2.0 and still exports ResponsiveConfig (3 word hits in its .d.ts), so the removed "spec still owns ResponsiveConfig" row was passing and, with no local dialect left to reclaim the natural name, guarded nothing. Non-blocking prose nit: the new header sentence says "the spec re-publishing GestureConfig or ResponsiveConfig would collide with no export here" — the spec still publishes ResponsiveConfig; that row's tripwire direction was retirement, not re-publication.
  9. declare module augmentation of either interface by an external consumer — accepted before, still accepted after, silently (measured in ③, channel C1). As implemented this is RIGHT — it is TypeScript's semantics, not a defect the PR could avoid short of a different route — but the PM ruling's description of this channel is WRONG (see ③).
  10. Liveness premise — intact. origin/main a3eb5d07a, git grep -w, CHANGELOGs and lockfile excluded: MobileResponsiveConfig 6 lines (declaration, both barrels, two parity rows, one finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 changeset sentence); GestureConfig 16 lines (declaration, both barrels, one index.ts comment, two mobile.ts JSDoc + one // comment, the useSpecGesture JSDoc, seven parity-test prose/row lines, one changeset sentence). Zero annotations, extends, casts, generic arguments, arrays or imports outside the two barrels. Controls GestureType 25, GestureContext 7, ResponsiveValue 11. Other channels in-repo: declare module '@object-ui/(types|mobile)' 0; export * from @object-ui/types/./mobile 0 (the only export * hit is components/src/custom/index.ts./mobile-dialog-content, unrelated); tracked .d.ts naming either 0; JS/JSON/MD code mentions 0. Sibling objectstack: MobileResponsiveConfig 0; GestureConfig 9, all about the spec's own retired GestureConfig(Schema) (release notes, upgrade guide, migration registry, interaction-config-retirement.test.ts), 0 importing from @object-ui; package.json files declaring any @object-ui/* dependency: 0; code lines importing @object-ui/types|mobile: 0 (the one regex hit is a changeset-frontmatter fixture string in scripts/objectui-changeset-digest.mjs:1571). Controls there: ObjectSchema 1267, FieldSchema 937, @objectstack/spec in 73 package.json. The author's "ComponentSchema 270" control does not reproduce with git grep -w on tracked files (1) — a different instrument, immaterial because my own controls lit. Structural use without naming the type is impossible: no value-level API in either package takes either shape.
  11. Scope fence — every edit beyond mobile.ts and the two barrels is entailed: useSpecGesture.ts (JSDoc only, emitted into dist/useSpecGesture.d.ts, item 6); the parity rows (the card itself asked for their disposition, item 8); the new pin test; the GestureType JSDoc and the // Spec-Touch note in mobile.ts (emitted, or would have become false). Diff is 7 files; registry.ts, complex.ts, ChartDataSeriesSchema untouched. No unrelated work rides along.
  12. New pin mobile-residue-retired-7519.test.tsRIGHT, red in the predicted channels (each leg restores by git checkout HEAD -- <abs paths> under an EXIT INT TERM trap, mutation proven by anchored counts and blob movement, restore proven by empty git diff HEAD, empty porcelain and blob equality):
    • Leg 0 (head): tsc -p packages/types/tsconfig.test.json exit 0, 0 diagnostics; --listFiles 563 files, the new test present, src/mobile.ts and src/index.ts in the program, 0 of this repo's dist/ (all 107 /dist/ entries are third-party under node_modules), 0 packages/mobile/ files; vitest 6/6.
    • P1 (mobile.ts + both barrels → base blobs 535a3f2179/1216ff51ca/d837454233): tsc exit 2, TS2578 ×4 at L63/65/71/73, 0 other; vitest 3 failed / 3 passed (the three source-read tests). Matches the author.
    • P3 (mobile barrel only): tsc exit 0 — the compiler leg is blind to @object-ui/mobile's barrel; vitest 1 failed (the mobile-barrel source-read test). That barrel is independently guarded: pnpm --filter @object-ui/mobile type-check on the same tree exits 2 with src/index.ts(65,3) TS2305 MobileResponsiveConfig and src/index.ts(72,3) TS2724 GestureConfig; at head it exits 0.
    • P4 (mobile.ts only): tsc exit 2, TS2578 ×2 at L65/L73 (the ../mobile.js spellings; the ../index.js directives stay satisfied because the barrel still does not re-export); vitest 1 failed (declaring-file test).
    • The tsc leg is a real gate: packages/types type-check = tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json, run by CI's "Type Check" job (success on head).
  13. Verification union at head, re-run by me: types type-check exit 0 (0 TS errors); mobile type-check exit 0 against the rebuilt types dist; vitest run packages/types/ packages/mobile/ from the repo root 107 files / 1757 tests passed (104 + 3 on disk); check-changeset-no-major and check-changeset-presence (6 source file(s) of 2 released package(s) ... 1 changeset) both exit 0. GitHub: all 32 check runs on the head completed success (3 skipped by design: dependabot, coverage), including Type Check, Lint, Build & E2E, all four test shards, README Export Check, Changeset Bump Policy, Changeset Declaration.

② Semver grading

Declared: '@object-ui/types': minor, '@object-ui/mobile': minor; both sit in the single 40-package fixed group of .changeset/config.json. AGENTS.md §版本号策略 (line 239): objectui's own breaking changes are marked minor with the breaking semantics spelled out in the body; major is reserved for the one synchronized bump that follows @objectstack across a major, and line 240 makes scripts/check-changeset-no-major.mjs (via changeset-guard.yml) refuse any major. The body opens "Removes two published exports.", states the exact outcome (import type { ... } from either package "is a compile error, not a deprecation warning", TS2305/TS2724), and says "Breaking for TypeScript consumers of the two names only". Correct level: minor. Declared level: minor. Graded RIGHT. Same classification as #5941 and #7526 for the same lineage. Structural note, not a defect of this PR: under this policy a ^17.x range absorbs the removal, which is what makes channel C3 below reachable for a stale published library — an exposure every breaking-as-minor change in this repo carries.

③ Boundary-flag disposition

Card #7519.

  • "A retirement has to decide what happens to the pin" — decided: the three rows are removed, proven phantom (item 8), and the absence is pinned in the new test in two channels (item 12).
  • "Retire vs implement is a product call" — the PM ruling (comment 5548523458) took retire; the implement path is preserved as the reopen condition in both // notes and the changeset (renderer lands with the declaration).
  • Ruling 5548523458's instructions: follow finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942's route — done (deletion, // notes, both barrels); check for a Zod mirror first — none exists (measured, item 7); both barrels go — done; external blast radius stated as "no consumers in this repo", not "no consumers" — the changeset says exactly that; scope fence — three extras, all entailed (item 11); file further zero-consumer types — none exist (GestureType 25 / GestureContext 7 / ResponsiveValue 11, all read by hooks).
  • Claim restatement 5548637182: read types: the triggerIcon tombstone's rationale contradicts mobile.ts's own retire-vs-remove discriminator, and its tripwire comment overclaims #7678 and say which prong — measured per type in the PR. Prong 1 no for both (0 readers of stackOnMobile/showOnly/stackBreakpoint; no action dispatcher; TouchInteraction.gestures is SpecGestureConfig[]). Prong 2 re-measured by me: CHANGELOG lines MobileResponsiveConfig 6 (one objectstack#4115 rename row × 6 package CHANGELOGs), GestureConfig 10 (the rename row × 6, the objectui#3363 reclaim note × 2, its digest line × 2); controls triggerIcon 1, MobileComponentConfig 0, MobileOverrides 0; @default/@example on the deleted declarations 0. Identical to the author's and the PM's counts.
  • Dev report's open question, Reading A vs B — the ruling chose A; I uphold A, on the grounds below rather than the ruling's.

PM ruling, comment 5548726381 (and its amendment 5548730153 on #7678). Judgment: disposition upheld; empirical premise refused.

The load-bearing claim — "the only way to reference it is an import, and a removed import is a named compile error... There is no channel in which this removal is quiet" — was tested on three surfaces: LIVE (base dist), DEL (head dist), TOMB (head dist plus a shim export type GestureConfig = never from both barrels, the only tombstone shape available). Each row is a separate tsc run; the first diagnostic is quoted.

channel LIVE DEL (this PR) TOMB (= never)
direct import type + value write 0 TS2305 / TS2724 at the import import silent; TS2322 'never' at the value
C1 declare module P { interface GestureConfig { meta?: string } } + widened write using meta 0 0 — SILENT TS2300 Duplicate identifier
C1b same augmentation, { meta: 1 } TS2322 number→string TS2322 number→string (the augmentation is a real type, not any) TS2300
C1c same augmentation, old shape { type, action } widened, no overlapping key 0 TS2559 (weak-type check — loud by accident of the augmentation being all-optional) TS2300
C2 export * from P compiled alone 0 0 (silent for the re-exporter) 0
C2 downstream import type { GestureConfig } from './mid' 0 TS2724 one hop later TS2322 at the value
C3 published lib .d.ts inlines import type { GestureConfig } from P, app compiles with skipLibCheck: true (this repo's root default and tsc --init's) TS2345 exit 0 — SILENT: the parameter collapsed to any, bind({ bogus: 1 }) accepted TS2345 'never'
C3 same, skipLibCheck: false TS2345 TS2724 inside the lib .d.ts TS2345
C3 control: same lib, GestureContext TS2345 TS2345 TS2345 (the instrument sees real types through the lib)
C4 JS + JSDoc @type, checkJs: true 0 TS2694 TS2322
C4 checkJs: false 0 0 0 (nothing is checked)
C5 type-position-only (cfgs: GestureConfig[], []) 0 TS2724 0 — SILENT
C7 import type * as T + T.GestureConfig 0 TS2724 0

Reverse control, the ruling's own premise about a surviving carrier (synthetic local interface Cfg { a?: string }): member deleted + widened value with an overlapping key → 0 (silent) vs tombstoned → TS2322; deleted + lone-key widened value → TS2559 (weak-type check); deleted + fresh literal → TS2353. So the "widened value goes unnoticed" premise holds only when the value shares a key with the carrier; #7669's literal { triggerIcon: 'Sparkles' } alone against the all-optional FloatingChatbotConfig trips TS2559 — a note for #7678's prose, not for this PR.

What the table decides:

PR #7683.

  • needs_decision — resolved by the ruling; the implementation is correct as it stands; no rework.
  • NOT MEASURED locally, declared: check:readme-exports — CI "README Export Check" success on the head; downstream type-check sweep — CI "Type Check" success on the head, plus mobile type-check exit 0 locally against the rebuilt types dist.
  • In-flight overlap — not re-scanned by me; measured instead: the two commits main is ahead by touch none of the PR's seven files, and feat(types)!: retire FloatingChatbotConfig.triggerIcon as an ADR-0049 tombstone (objectui#7654) #7669's tombstone is absent from the PR's tree (complex.ts:928 triggerIcon?: string at head vs ?: never on main) — a merge brings it in with no shared hunk.
  • mergeable_state: behind and draft status — the dispatcher's; CI ran on the merge ref and is green.
  • Two non-blocking nits, neither parks a PASS: the parity-header sentence in item 8; the changeset's Upgrading section could add one line for consumers who augmented either interface via declare module (the augmentation now silently declares a fresh type — delete it). The C3 population cannot be reached by a changeset at all; the library author is warned loudly when they rebuild their own .ts.

#7678.

  • Item 1 (contradictory rules in complex.ts:967 vs mobile.ts:129) — dissolved by the carrier precondition; re-word its justification per the paragraph above before inscribing.
  • Item 2 (the tripwire comment in floating-chatbot-trigger-icon-retired.test.ts) — out of this PR's scope, untouched, still open there.
  • The amendment's "one measurement in the body needs correcting" (6 / 10 CHANGELOG lines) — confirmed; and I agree those lines instruct the import in as many words, so prong 2 is met on its literal wording and is not the discriminator.
  • Add to that card: the widened-value premise needs the overlapping-key qualifier (reverse control above).

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types

2 participants