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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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-zhuangos-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:

RetirementPullNamed live replacementForm
ToggleGroupItem.icon (objectui#4632, 2026-08-14)zerononeremoved outright
AccordionItem.icon (objectui#4652, 2026-08-15)zerononeremoved outright
BaseFieldMetadata.indexedzeroobject-level indexesremoved outright
theme spacing / breakpoints / densitynever consumednoneremoved outright
action confirm (objectui#4314, 2026-08-18)zero producers, but ActionRunner read itconfirmText?: never tombstone
hoverable / striped (objectui#5474, 2026-08-23)docs taught them as workingdata-table / className?: never tombstone
theme fontSize / fontWeightdead engine emissioncustomVars, 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.tsmenu, base.tsvisibleOn, 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:

ProbeBeforeAfter
A — import type { MobileOverrides } from '@object-ui/types'exit 0, silentTS2305: Module '"@object-ui/types"' has no exported member 'MobileOverrides'
B — author mobileOverrides: { navigation: 'bottom-tabs' } on a MobileComponentConfigexit 0, silentTS2353: 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 0exit 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

MetricValueBudget
Eager closure (gzip, 52 chunks)3230.7 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-BL8yIXhK.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)504.18KB114.10KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.32KB34.42KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)243.38KB61.72KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.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 queueAug 24, 2026
Merged via the queue into main with commit 2d7304dAug 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 freeto 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

@os-zhuang@claude