Skip to content

feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) - #5941

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4919-retire-mobile-overrides
Aug 24, 2026
Merged

feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919)#5941
os-zhuang merged 1 commit into
mainfrom
claude/issue-4919-retire-mobile-overrides

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #4919

Carries the recorded maintainer ruling on that card (comment 5339691949, 「全部接受」): retire the whole MobileOverrides type — the declaration, its mobileOverrides? mount point, and both barrel re-exports. The three-value navigation vocabulary is not narrowed and not kept in any form. Implementing mobile overrides was rejected there and is not attempted here.

Which tombstone branch, and the precedent that decided it

Branch (a): one-stage removal. This PR completes the card; there is no stage 2 outstanding.

The dispatch required this be measured, not assumed — specifically, not assumed from the metadata-spec retiredKey() / ADR-0087 registry path, which governs spec keys rather than TypeScript types. It does not apply here: packages/types/src/mobile.ts has no Zod twin (there is no mobile.zod.ts in packages/types/src/zod/), so the "loud parse rejection in the Zod twin" half of that discipline has nothing to attach to.

What this repo actually does when retiring a published TS surface is two coexisting forms, and the discriminator is not chronology — both are live and interleaved:

Retirement Pull Named live replacement Form
ToggleGroupItem.icon (objectui#4632, 2026-08-14) zero none removed outright
AccordionItem.icon (objectui#4652, 2026-08-15) zero none removed outright
BaseFieldMetadata.indexed zero object-level indexes removed outright
theme spacing / breakpoints / density never consumed none removed outright
action confirm (objectui#4314, 2026-08-18) zero producers, but ActionRunner read it confirmText ?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23) docs taught them as working data-table / className ?: never tombstone
theme fontSize / fontWeight dead engine emission customVars, byte-for-byte ?: never tombstone

The rule the table shows: a ?: never tombstone exists to steer authors to a named live replacement, or to keep loud a key the docs actively taught. Outright removal is for zero pull with no successor.

This card is the second row's shape exactly. AccordionItem.icon states it in its own changeset: it "had zero measured pull anywhere in the corpus and no established convention to lean on, so under this platform's declared=enforced doctrine it is removed rather than speculatively implemented." MobileOverrides has zero pull, was never documented anywhere, and has no successor spelling — the ruling explicitly forbids keeping the vocabulary in any form. Verified in source: icon is fully absent from both AccordionItem and ToggleGroupItem today — no ?: never, no @deprecated residue.

There is no two-stage @deprecated-then-remove convention in this repo. Every @deprecated in packages/types/src is one of two things, and neither is a stage 1 of retirement: a still-working legacy alias (app.ts menu, base.ts visibleOn, the MenuItem and ActionSchema interfaces — all still typed and still read), or a tag applied in the same commit as the ?: never retyping, where the tombstone is the terminal state. Deprecating a no-op for a release cycle would ship a release in which mobileOverrides still type-checks and still does nothing — prolonging the exact declare-without-enforce shape the ruling exists to close.

Re-verified zero-read measurement

The card is dated 2026-08-17, so its premise was re-measured against origin/main at ed35c23bb before anything was deleted. It holds.

MobileOverrides had exactly four mentions repo-wide, none of them a read:

packages/types/src/index.ts:585    barrel re-export
packages/types/src/mobile.ts:73    the declaration
packages/types/src/mobile.ts:283   mobileOverrides?: MobileOverrides   (the mount point)
packages/mobile/src/index.ts:60    barrel re-export

The lower-case property name — the spelling a renderer would actually read — appeared only in that declaration. A case-insensitive sweep across every file type (not just .ts/.tsx) found no JSON metadata, no docs, no schema-catalog entry, and no example app authoring it. The objectstack and objectstack-ai sibling checkouts: zero hits.

Of the three navigation values, bottom-tabs and hamburger occur nowhere else in source except the declaration line itself. The other hamburger hits belong to MobileNavMode in packages/layout — the objectui#3985 sibling, a different type on a different consumption path, untouched here.

dist evidence — before and after, same grep

Proving the type is gone from src/ proves nothing about what consumers see, so the emitted declarations were measured on both sides of the change. Identical pattern, identical scope (packages/*/dist --include='*.d.ts'), rebuilt each time:

Before (the control — the instrument demonstrably sees the case):

packages/mobile/dist/index.d.ts:36   export type { ... MobileOverrides ... }
packages/types/dist/index.d.ts:73    export type { ... MobileOverrides ... }
packages/types/dist/mobile.d.ts:63   export interface MobileOverrides {
packages/types/dist/mobile.d.ts:262      mobileOverrides?: MobileOverrides;
  -> MobileOverrides: 4 hits across 3 files
  -> mobileOverrides:  1 hit
  -> 'bottom-tabs':    1 hit  (mobile.d.ts:75)

After (pnpm --filter @object-ui/types --filter @object-ui/mobile build, exit 0):

  -> MobileOverrides: 0 hits
  -> mobileOverrides:  0 hits
  -> 'bottom-tabs':    0 hits

The source retirement note left in mobile.ts names the type in a comment; the zero MobileOverrides count in dist confirms it does not leak into the published .d.ts.

The loud-signal requirement, measured rather than asserted

The ruling asks that external .d.ts consumers "get a loud signal rather than a silent no-op". That was tested with a real consumer compiled against the built .d.ts, on both sides of the change, with a control:

Probe Before After
A — import type { MobileOverrides } from '@object-ui/types' exit 0, silent TS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfig exit 0, silent TS2353: Object literal may only specify known properties, and 'mobileOverrides' does not exist in type 'MobileComponentConfig'
C — control: responsive (a still-live key on the same interface) exit 0 exit 0

Row B before the change is the card's defect reproduced exactly: it type-checked, it built, and nothing happened. The control matters — on its first run the whole probe failed on a config error rather than on the retirement, which would have made row A's "failure" indistinguishable from success; C staying green is what shows the instrument is measuring the right thing.

Excess-property checking is load-bearing for row B, so it was confirmed that MobileComponentConfig carries no index signature and no extends — nothing to swallow the diagnostic. (BaseSchema's [key: string]: any does exactly that for top-level component-schema keys; this interface is not in that family.)

The before leg reverted the three files to origin/main, confirmed the mutation on disk by anchored counts on both the deleted and injected text, rebuilt so the mutation actually reached dist/, and ran under a restore trap. Restore was verified byte-exact: git status clean against the commit, and dist back to zero hits.

Verification

Ran from the repo root with path filters, per AGENTS.md.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0 (both script names echoed, so this was not a zero-match silent pass)
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 56 passed (56), Tests 601 passed (601). 56 = 52 types + 4 mobile, so the filter matched these packages rather than someone else's
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 0 errors, 253 warnings (types) and 0 errors, 18 warnings (mobile), all pre-existing in examples/ and __tests__/, none in the changed files
  • check-control-bytes — OK, 4929 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence — 1 changeset for 3 source files of 2 released packages
  • check-changeset-no-major — no changeset declares major
  • check-spec-symbol-derivation, check-published-dist-tooling, check-doc-component-types — all exit 0

All gate results are quoted from each gate's own verdict line, with exit codes captured before any pipe.

One honest note on the lint run: an earlier pass used --no-inline-config, which is not this repo's lint command (eslint . is), and it reported one error in packages/types/src/index.ts:1179 by disabling a documented, pre-existing eslint-disable block for a FormField spec import at line 1178 — unrelated to this change and present on main. Under the repo's actual command that file is clean.

Verification union ran against the final commit, da50609e0.

Scope

Three source files and one changeset. packages/app-shell was never path-widened into.

The changeset is minor, not major, per this repo's version-alignment rule, which reserves major for following @objectstack across a major — the same classification AccordionItem.icon used for an identically breaking type removal. It records the change as breaking for TypeScript authors of mobileOverrides only, with runtime behaviour unchanged.

Out of scope, and deliberately left alone here: MobileComponentConfig itself now has zero mount points and zero readers — it is declared and re-exported twice and nothing consumes it. Same family as this card but a different type, and the #4919 ruling does not cover it, so it is filed for triage as #5942 rather than widened into this PR.


Generated by Claude Code

…int (#4919)

`MobileOverrides` published a six-key mobile override surface — including a
three-value `navigation` vocabulary — from `@object-ui/types` and, re-exported,
from `@object-ui/mobile`. Nothing read any of it: measured on current `main` the
type had exactly four mentions repo-wide (declaration, the single
`MobileComponentConfig.mobileOverrides` mount point, two barrel re-exports), and
a sweep of the example apps and the `objectstack` sibling checkout found zero
authors. All three `navigation` values were spellings of the same no-op.

Removed rather than narrowed (the #3985 lineage's "narrow to the implemented
values" rule terminates in deletion when the implemented set is empty), and
removed rather than `?: never`-tombstoned: a tombstone in this package steers
authors to a named live replacement (`confirm` -> `confirmText`;
`hoverable`/`striped` -> `data-table`), and there is no successor here. Same
zero-pull, no-successor shape as the retired `AccordionItem.icon` (#4652) and
`ToggleGroupItem.icon` (#4632), which were removed outright.

Runtime behaviour is unchanged; what changes is that the contract stops claiming
otherwise, so authoring the key becomes a compile-time error instead of a silent
no-op.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3230.7 KB 3990.2 KB
Main entry chunk (gzip) 153.6 KB 350 KB
Entry file index-BL8yIXhK.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) 10.13KB 3.77KB
app-shell (runtime-config.js) 13.57KB 4.78KB
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) 504.18KB 114.10KB
core (index.js) 4.92KB 1.97KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 164.55KB 45.67KB
fields (index.js) 238.40KB 59.89KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 23.13KB 7.63KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.95KB 10.97KB
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.55KB 0.62KB
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 (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.53KB 3.38KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.64KB 1.50KB
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) 1.93KB 0.88KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.66KB 18.32KB
plugin-chatbot (index.js) 188.21KB 44.67KB
plugin-dashboard (index.js) 133.32KB 34.42KB
plugin-designer (index.js) 212.30KB 42.80KB
plugin-detail (index.js) 243.38KB 61.72KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 125.63KB 30.64KB
plugin-gantt (index.js) 164.15KB 39.88KB
plugin-grid (index.js) 200.79KB 54.26KB
plugin-kanban (index.js) 52.93KB 14.60KB
plugin-list (index.js) 111.86KB 27.22KB
plugin-map (index.js) 20.06KB 6.62KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.49KB 11.93KB
plugin-timeline (index.js) 26.49KB 7.59KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.57KB 20.74KB
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) 3.77KB 1.33KB
react (SchemaRenderer.js) 52.40KB 17.45KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.35KB 0.70KB
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) 12.13KB 3.65KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 7.54KB 2.63KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 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 (index.js) 3.88KB 1.85KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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 (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) 3.40KB 1.71KB
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

@os-zhuang
os-zhuang marked this pull request as ready for review August 24, 2026 02:50
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit 2d7304d Aug 24, 2026
23 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4919-retire-mobile-overrides branch August 24, 2026 02:51
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): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」

2 participants