feat(types,mobile)!: retire the MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@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 MobileComponentConfig published type - #7526

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire
Sep 3, 2026
Merged

feat(types,mobile)!: retire the MobileComponentConfig published type#7526
os-project-manager merged 2 commits into
mainfrom
claude/issue-5942-mobilecomponentconfig-retire

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#5942

Retires MobileComponentConfig via the ADR-0049 enforce-or-remove route, following the MobileOverrides retirement pattern (objectui#4919, PR objectui#5941) that triage named. This narrows a published surface, so the PR is a draft carrying needs:contract-review and is for the docket to clear, not for me to land.

The card is dated 2026-08-24. Every premise was re-measured against origin/main at a27d153c2 before anything was deleted, and all of them hold.

Patch round: what contract review caught, and the fix

Review returned FAIL (narrow) on c0eb3eb2f and it was right. The defect was in this PR's own doc edit, on the very surface it claims to protect.

The retirement note is a // line comment, which TypeScript's declaration emit strips. The rewritten JSDoc on MobileResponsiveConfig pointed at it with "see the RETIRED note below", and JSDoc is emitted. So in the published packages/types/dist/mobile.d.ts the pointer survived and its target did not — one dangling reference traded for another, this time on the published surface. The same paragraph said "widened into that PR", which no external reader of a .d.ts can resolve. Reproduced independently before fixing, on the review tree at c0eb3eb2f:

packages/types/dist/mobile.d.ts
grep -c 'see the RETIRED note below' = 1 <- emitted
grep -c 'RETIRED (objectui#5942' = 0 <- target not in the published file

Why I did not catch it myself, stated plainly: my after-leg dist measurement checked only the two index.d.ts barrels and reported "0/0". objectui#5941, the precedent I was copying, measured all of packages/*/dist. Having changed the comment mechanism — moving explanation into a live type's JSDoc — I silently lost the property that caliber was protecting. Narrowing a measurement without declaring it moves the evidence away from where the defect is.

The fix (37f82c558) rewrites the JSDoc so every reference resolves for someone reading only the .d.tsobjectstack#4115, objectui#5942, PR objectui#7526, objectui#7519, and a repo-relative test path, each verified present in the emitted file — and it no longer names the retired symbol at all. That is deliberately not a declared divergence from objectui#5941: dropping the name restores that PR's property exactly, so the retired name now occurs zero times across every emitted declaration rather than only in the barrels. It also drops "for objectui's mobile component overrides" from the same block, which named the concept objectui#4919 retired and so was stale twice over.

Two separate measurements, because "no reader in the repo" and "not published" are different facts

The card says "four mentions, all barrels/self-references". That phrasing is consistent with the type being on the published surface, and it is — measured, not inferred.

Table 1 — in-repo readers: zero

Read-shape probes over the whole tree, all file types. Every zero is paired with a control lit in the same run, because a probe whose query shape is wrong returns zero too.

Probe (shape, not bare word)MobileComponentConfigControl, same run
type annotation : S0ComponentSchema = 11
array S[]0ComponentSchema[] = 1
generic arg <S0Record = 815
extends S0BaseSchema = 159
implements S0any-implements = 21
cast as S0Record = 1053
union member | S0SchemaNode = 103
intersection & S0*Schema = 2
keyof / typeof S0keyof = 342
import specifier line2(self-lit: the two barrels)
declaration export interface S1any-declaration = 2316

Sibling objectstack checkout, case-insensitive, all file types: 0 hits, with ComponentSchema lit at 255 hits in the same run.

Bare-word counting would have been wrong here, which is exactly why the table is shaped this way. MobileComponentConfig has seven bare-word occurrences today, not four: objectui#4919's own retirement note and its pending changeset both name the type in prose.

That count depends on where you cut, so here is the cut. Three occurrences are load-bearing code — the declaration and the two barrel re-exports. A fourth is a JSDoc cross-reference, which is code-adjacent in a way that turned out to matter: declaration emit publishes it, which is exactly how this PR's first round went wrong. A fifth lives in a // line comment inside mobile.ts and is stripped from emit, so by the same test it is prose. The card's "four" is the first four under that cut; "5 mentions in .ts files" and "3 real code references" are the same tree measured differently, and the bare number should not be forwarded without the cut. Under every cut, the number of reads is zero:

packages/types/src/index.ts:626 barrel re-export
packages/types/src/mobile.ts:50 a doc-comment cross-reference ({@link ...})
packages/types/src/mobile.ts:283 the declaration itself
packages/mobile/src/index.ts:68 barrel re-export

Members are responsive, gestures, pullToRefresh, infiniteScrollmobileOverrides removed by objectui#5941, as the card predicted. Premise intact on every axis: zero mount points, zero readers, four code mentions.

Table 2 — published-surface reachability: it IS published, from two packages

EvidenceResult
packages/types barrelsrc/index.ts:626, inside export type { ... } from './mobile.js' — the package ROOT barrel, one hop
packages/mobile barrelsrc/index.ts:68, inside export type { ... } from '@object-ui/types' — root barrel, one hop
@object-ui/typesexports map"." -> ./dist/index.d.ts; package is not private, version 17.6.0
@object-ui/mobileexports map"." -> ./dist/index.d.ts; not private, 17.6.0
built packages/types/dist/index.d.tsline 81 — the name is in the emitted declaration
built packages/mobile/dist/index.d.tsline 36 — same

The ./mobile subpath is not in the exports map and there is no wildcard subpath, so packages/types/dist/mobile.d.ts is not directly importable — but the root barrel re-exports the name, so it reaches consumers anyway. That is the distinction that matters: reachability is a property of the barrel chain plus the exports map, not of the declaring file.

Conclusion: Clause-2 yes. This is a breaking narrowing of a published surface, and the card's own description matches what is on disk.

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

A removal from src/ proves nothing about what consumers see, so an external consumer was compiled against the built .d.ts through the real exports map (its own directory, its own node_modules symlinks, moduleResolution: bundler), on both sides of the change.

ProbeBeforeAfter
A — import type { MobileComponentConfig } from '@object-ui/types'exit 0, silentTS2724: '"@object-ui/types"' has no exported member named 'MobileComponentConfig'. Did you mean 'ComponentConfig'?
A2 — same import from @object-ui/mobile (the second barrel)exit 0, silentTS2305: Module '"@object-ui/mobile"' has no exported member 'MobileComponentConfig'.
C — control: MobileResponsiveConfig, a still-live export from the same file and the same barrelsexit 0exit 0

Row A/A2 before the change is the card's defect reproduced exactly: the dead type imports cleanly from both published packages and type-checks. The control is what makes the after column mean something — if the probe had been misconfigured, A would have "failed" for a reason indistinguishable from success.

Ablation discipline, both legs: the mutation was confirmed on disk by anchored counts in both directions (deleted-text anchor export interface MobileComponentConfig 0 to 1, injected-text anchor RETIRED (objectui#5942 1 to 0), each leg was rebuilt, and the mutation was confirmed to have reached dist before any reading was taken (types/dist/index.d.ts and mobile/dist/index.d.ts at 1/1 on the before leg, 0/0 on the after leg). The whole script ran under a restore trap using absolute paths. Restore was proven by observed state, not by an exit code: git diff HEAD empty, git status --porcelain empty, and all three files byte-exact against their HEAD blob hashes, then rebuilt back to zero.

The consumer probe was re-run unchanged at the patched head 37f82c558 and returns the same two diagnostics, since the patch round touches doc comments only and moves no exported name.

dist evidence at the whole-dist caliber

This is the measurement the first round narrowed and review restored. Rebuilt at 37f82c558, the two commands review named plus the widest form:

grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'
-> 0 hits
grep -c RETIRED packages/types/dist/mobile.d.ts
-> 0 (the `//` note is correctly NOT published; that is the intent)
grep -rho MobileComponentConfig packages/*/dist --include='*.d.ts' | wc -l
-> 0 (ALL packages -- the caliber objectui#5941 reported)

The three strings review flagged are gone from the emitted file: see the RETIRED note below 1 to 0, widened into that PR 1 to 0, mobile component overrides0. Controls in the same run, so a zero is a reading and not a broken instrument: MobileResponsiveConfig still occurs 3 times across packages/*/dist, and every reference the new JSDoc makes is present in the emitted mobile.d.tsobjectstack#4115 (6), objectui#5942 (1), objectui#7526 (1), objectui#7519 (1), page-nav-misc-spec-parity.test.ts (4).

Which retirement branch, and the precedent it copies

Branch (a): one-stage removal, source retirement note, no ?: never. This PR completes the card; there is no stage 2.

Copied from objectui#5941 (MobileOverrides), which states the discriminator on this exact file. Stated honestly, because review checked: objectui#5941 was merged with zero reviews (opened 02:37, self-merged 02:51), so it "established" nothing procedurally. What carries authority is the outcome shape it implements, which does rest on objectui#4919's recorded maintainer ruling, plus the retirements it tabulates (AccordionItem.icon, ToggleGroupItem.icon, confirm, hoverable/striped) — each independently checkable in source. The discriminator stands on that evidence, not on the precedent PR's review status:

  • a ?: never tombstone exists to steer authors to a named live replacement key, or to keep loud a key the docs actively taught as working;
  • outright removal is for zero pull with no successor.

Neither tombstone reason applies, and one of them is structurally impossible here: MobileOverrides was a type reached through a key (mobileOverrides?:) on a surviving interface, so a never key had somewhere to live. MobileComponentConfig has no mount point at all — the whole interface goes, and there is no surviving object to hang a never key on. Keeping the interface with all four members retyped never would publish a carcass nothing could reach, which is the declare-without-enforce shape the route exists to close.

On the docs half: no published documentation ever described this type.skills/objectui/guides/mobile.md teaches the React hooks (useGesture, usePullToRefresh, ResponsiveContainer, useBreakpoint) and never names MobileComponentConfig or its keys as a metadata surface; zero occurrences in any .md/.mdx in the repo outside objectui#5941's own changeset prose.

No ledger verdict is demanded, checked rather than assumed. The ADR-0087 / retiredKey() registry path governs metadata-spec keys with a Zod twin; packages/types/src/zod/ has no mobile.zod.ts, so the loud-parse-rejection half has nothing to attach to. zod/tombstone.zod.ts is the ADR-0049 helper for schema keys and names nothing here. internal/retired-field-keys.ts is a FIELD-key registry for the designer strip sites; a type name is not a field key. objectui has no liveness-ledger machinery. Triage's stop-and-report conditions were therefore all checked and none fired.

What changed

Three source files and one changeset — the same footprint as objectui#5941.

  • packages/types/src/mobile.ts — the declaration replaced by the RETIRED note (the tombstone), carrying the measurement, the discriminator, the fact that no behaviour is retired, and the reopen condition.
  • packages/types/src/mobile.ts — the {@link MobileComponentConfig} cross-reference in MobileResponsiveConfig's doc comment rewritten. It would otherwise dangle at a removed name, and the sentence it made ("It is consumed only by MobileComponentConfig") would be false.
  • packages/types/src/index.ts — barrel re-export removed.
  • packages/mobile/src/index.ts — barrel re-export removed.
  • .changeset/5942-retire-mobile-component-config.mdminor for both packages.

The changeset opens with "Removes a published export." and carries an explicit ## Upgrading section rather than leaving the break implicit. Its guidance, verbatim:

  • You imported the type only (the only thing that was possible — nothing accepted it as a value): delete the import. If you kept a local config object annotated with it, drop the annotation; the object was never passed anywhere that read it.
  • You actually wanted the behaviour: it exists, and it is not being retired. It lives in @object-ui/mobile as React hooks, which is where the working code always was — useResponsive / ResponsiveContainer for responsive, useGesture for gestures, usePullToRefresh for pullToRefresh. infiniteScroll has no hook; it was never implemented in any form.
  • You want a declarative mobile config surface: that re-enters deliberately as designed product surface on its own card, with the renderer that reads it landing in the same change as the declaration — not by restoring this declaration.

minor not major, per AGENTS.md 版本号策略, which reserves major for following @objectstack across a major — the same classification objectui#5941 used for an identically breaking type removal. check-changeset-no-major confirms it.

Consequence worth the docket's attention, deliberately not widened into this PR

MobileComponentConfig was the sole consumer of two other published types declared in the same file:

packages/types/src/mobile.ts:285 responsive?: MobileResponsiveConfig; <- only consumer
packages/types/src/mobile.ts:287 gestures?: GestureConfig[]; <- only consumer

After this change both are zero-consumer published types, one level down the same lineage. Visible in the dist controls: MobileResponsiveConfig 4 hits to 3, GestureConfig 7 hits to 6 — each losing exactly its mount point and nothing else.

Filed for triage as objectui#7519 rather than widened here, for the same reason objectui#5941 filed this card instead of widening: MobileResponsiveConfig carries a spec name-ownership tripwire that outlives the type, and retire-vs-implement is a product call. Their live siblings on the same file — GestureType, GestureContext, ResponsiveValue — are genuinely read by useGesture.ts, useSpecGesture.ts, breakpoints.ts and useResponsive.ts, and are untouched.

Verification

Verification union re-ran in full against the final commit, 37f82c558, with a clean tree; the figures below are that run, not the first round's. Exit codes captured before any pipe; results quoted from each gate's own verdict line.

  • pnpm --filter @object-ui/types --filter @object-ui/mobile build — exit 0; dist completeness: 118 emitted files verified (types) and 51 (mobile)
  • pnpm --filter @object-ui/types --filter @object-ui/mobile type-check — exit 0; both script names echoed (type-check is hyphenated in this repo), so not a zero-match silent pass
  • pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=2Test Files 99 passed (99), Tests 1651 passed (1651). 99 = 95 types + 4 mobile counted on disk, so the filter matched these packages and nothing else
  • pnpm --filter @object-ui/types --filter @object-ui/mobile lint — exit 0; 180 files linted (159 + 21), 0 errors, 285 warnings all pre-existing in tests/examples. All three changed files: 0 errors, 0 warnings
  • check:control-bytes — OK, 6195 tracked text files scanned; plus a manual control-byte scan of the four changed files
  • check-changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check-changeset-no-major / check-changeset-fixed / check-changeset-overwrite — exit 0
  • check:published-distNo published package's build output carries tooling material (this one builds all 43 packages first, 6m50s, which is why the run below is worth having)
  • check:readme-exportsnow green on a FULL population, which the first round could not achieve: 421 of 421 self-imports judged real, 0 fabricated, 0 wrong-path, 3310 export symbol(s) read from 37 of 40 tracked package(s), 0 unbuilt. In round one this gate exited 1 with the population COLLAPSED -- this run proves nothing because only 3 of 40 packages were built in that worktree; that was a prerequisite failure, not a verdict, and it is now resolved rather than argued around. Removing an export from two barrels is exactly the change this gate exists to catch, so it should be a real reading and now is
  • check:spec-symbols, check:doc-types, check:self-import, check:phantom-deps — all exit 0

One honest note on scope:

  1. Lint was narrowed to the two changed packages, declared as a narrowing. The population is each package's own eslint . (this repo's real lint command and its real granularity — lint is a per-package turbo task), the file counts are eslint's own --format json output, and type-aware linting is not configured in eslint.config.js (no project, no projectService, no recommendedTypeChecked), so this diff cannot move the verdict of any file it did not touch. The repo-wide farm is CI's run.

Scope

Three source files and one changeset. No content/docs/releases/ edit. No gate ceiling raised, no test skipped or disabled. Left as a draft with needs:contract-review for the docket.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`MobileComponentConfig` published a four-key mobile component-schema
extension (`responsive`, `gestures`, `pullToRefresh`, `infiniteScroll`)
that nothing read. Re-measured on current main: four code mentions
repo-wide (declaration, one doc cross-reference, two barrel re-exports),
no mount point, no reader, zero authors in the example apps or the
objectstack sibling checkout.
Removed outright rather than tombstoned with `?: never`: the whole
interface goes, so there is no surviving object to hang a never-key on,
and no documentation ever taught it. Same zero-pull, no-successor shape
as MobileOverrides (objectui#4919).
The dangling `{@link MobileComponentConfig}` cross-reference on
MobileResponsiveConfig is rewritten rather than left pointing at a
removed name; that type and GestureConfig become zero-consumer published
types as a result, recorded as objectui#7519 rather than widened here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核:FAIL(窄口径) —— 一处需改,其余全部独立复现

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC。本席是 opus,不在CONTRACT_REVIEW_TIER,故此裁由受托复核者出。

转录档位核验(维护者 2026-08-27 裁),采信前先做:该复核者转录 73 个 assistant 轮次,73 个全部 stamped claude-fable-5-1,零未 stamp,model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中。⇒ 达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废,⛔ 不得改写、删节或润色。以下是复核者原文。

⛔ 简报中包含本席的任何判断,这是刻意的 —— 独立复核的意义是重新推导,不是确认。


裁断:FAIL(窄口径——只有一处需要改,其余全部复现)

被击穿的一点:PR 把 MobileResponsiveConfig 上那句 {@link MobileComponentConfig} 改写,理由是"否则会悬空指向一个已删除的名字"。改写后的段落是 JSDoc,会被 tsc 原样 emit 进已发布的 packages/types/dist/mobile.d.ts;而它指向的 // RETIRED (objectui#5942 …) 是行注释,declaration emit 会剥掉。结果是已发布的 .d.ts 里出现 "(see the RETIRED note below)",而 below 什么都没有——PR 亲手用一个悬空引用换掉了另一个悬空引用,且发生在它声称要保护的已发布面上。PR 的 after 腿 dist 测量只覆盖了两个 index.d.ts("0/0"),没测 mobile.d.ts,所以自己没看见;它照抄的先例 #5941 测的是整个 packages/*/dist 并报告名字为 0——#7526 换了注释机制(活类型上的 JSDoc)后静默地失去了这一性质。


一、我独立复现的读数(命令 + 数字)

基线:GitHub 报 base fe4e7a9e8(当前 main 尖端),实际 fork 点 git merge-base c0eb3eb2f origin/main = a27d153c2,与 PR 自称的测量基线一致。git log a27d153c2..fe4e7a9e8 -- <三个源文件> 为空,主干没动过这三个文件;下面所有计数在两个基线上逐字相同。

攻击点 1:计数

git grep -n "MobileComponentConfig" a27d153c2 -- .7 行 / 7 次(-o)/ 4 文件,大小写不敏感也是 7。逐行:

.changeset/4919-retire-mobile-overrides.md:14 prose
.changeset/4919-retire-mobile-overrides.md:41 prose ← 同一 changeset 里是 2 处,PR 说"its pending changeset"名了它,数目 7 仍对
packages/mobile/src/index.ts:68 barrel
packages/types/src/index.ts:626 barrel
packages/types/src/mobile.ts:50 {@link} doc 注释
packages/types/src/mobile.ts:74 #4919 的 // RETIRED 注释
packages/types/src/mobile.ts:283 declaration

PR 列的四处行号(626 / 50 / 283 / 68)逐字命中。注意分类脆弱.ts 文件内是 5 处;"真正的代码"(声明 + 两 barrel)是 3 处;"恰好 4"只在"把 :50 的 doc 注释算代码、把 :74 的行注释算 prose"这一种切法下成立。PR 对 :50 标注了"doc-comment cross-reference",所以不算错,但这个 4 不宜被下游当硬数转发。
PR head c0eb3eb2f:8 行 / 3 文件,0 处代码提及(无声明、无 barrel)。

攻击点 3:"唯一消费者"

git grep -nw GestureConfig a27d153c2 -- . 排除 CHANGELOG 后:类型级消费只有 mobile.ts:287 gestures?: GestureConfig[]packages/mobile/src/useSpecGesture.ts:66 是 doc 注释,该文件第 10 行实际 import type { GestureType, SpecGestureConfig }useGesture.ts:10 只 import GestureType, GestureContextMobileResponsiveConfig 同理,唯一消费 mobile.ts:285,另有 parity 测试 :608/:749 两处字符串字面量(名字归属 tripwire,不是读点)。"唯一"成立。dist 对照:grep -rnwpackages/{types,mobile}/dist/**/*.d.tsMobileResponsiveConfig4→3GestureConfig7→6,各自恰好少掉挂载行——与 PR 一致。另立 #7519 而不在本 PR 处理:合理,MobileResponsiveConfig 的名字归属 tripwire(page-nav-misc-spec-parity.test.ts:749 断言 spec 不拥有该名)确实独立于类型存亡。

③ 边界标记

  • Table 1:全树 7 行已逐行归类,无一是注解/extends/cast/泛型参数;/home/user/objectstack 兄弟仓 git grep -il mobilecomponentconfig = 0,对照 ComponentSchema 76 个文件点亮。
  • Table 2:packages/types/package.json exports "."→./dist/index.d.ts,子路径只有 base/layout/form/data-display/feedback/overlay/navigation/complex/data/zod/internal/retired-field-keys,./mobile、无通配、无 typesVersions;mobile 只有 ".";两包都非 private、17.6.0;.changeset/config.json 两包都在 fixed 组。全仓 export (type )?\* from '@object-ui/types' = 0 命中,不存在第三个包让面可达
  • 两条腿各自 pnpm install --frozen-lockfile + pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,dist completeness 118 / 51)。before 腿:types/dist/index.d.ts:81mobile/dist/index.d.ts:36 行号精确命中。

攻击点 2:外部消费者探针

scratchpad 独立目录,node_modules/@object-ui/{types,mobile} 符号链接到各腿的包目录,moduleResolution: bundler--traceResolution 确认解析到 packages/types/dist/index.d.ts,Package ID @object-ui/types/dist/index.d.ts@17.6.0(走了 exports map)。

探针beforeafter
A import type { MobileComponentConfig } from '@object-ui/types'exit 0TS2724 "Did you mean 'ComponentConfig'?"
A2 同名 from @object-ui/mobileexit 0TS2305
C 对照 MobileResponsiveConfig(两包)exit 0exit 0
N 负对照(PR 没做)DefinitelyNotAnExportQqqTS2305TS2305
D 深路径(PR 没做)@object-ui/types/mobileTS2307TS2307

N 在 before 腿就报错,证明 before 腿的 exit 0 是"真解析到名字"而不是仪器把整包解析成 any;D 证明 exports map 没有子路径逃逸。探针设计成立,对照是为它声称的原因通过的。

① 派生判断

  • 判别式来源feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 正文/changeset/源码注释三处都写了同一条规则,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 是照抄,不是编的。它引用的例子我在源码逐一核实:crud.ts:124 confirm?: never@deprecated 指向 confirmText)、data-display.ts:653/663 hoverable?/striped?: neverdisclosure.ts 全文 icon 0 命中;packages/types/src 非测试文件中 = never; 整类型别名 0 处——不存在"整类型 = never 墓碑"先例,"墓碑只用于键"与仓库现状一致。
  • "结构上不可能":措辞过头——never 成员的空壳或 type X = never 别名在结构上都可写,PR 下一句自己也承认并按理由否决;准确的主张是"没有父对象可挂 never 键",这一条为真(零挂载点已验)。判断成立,措辞不严。
  • 无需 ledger 裁定git ls-tree a27d153c2 packages/types/src/zod/mobile.zod.tsretiredKey 定义在 zod/tombstone.zod.ts,是 Zod 键墓碑助手;internal/retired-field-keys.ts 头注释明写是 FIELD-key 注册表;objectui 里 "ledger" 命中全是无关语义(screencast ledger、UNGATED_DOCS 等),liveness ledger 在 ../objectstack,那边对该名 0 命中。三条全部成立。

② semver

AGENTS.md:225 逐字:"objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可);唯一例外是跟随 objectstack 跨 major"。分级正确。check-changeset-no-major / -fixed / -overwrite / -presence 四门在 after 腿全 exit 0(presence:"3 source file(s) of 2 released package(s) changed … 1 changeset(s)")。changeset 首句 "Removes a published export.",含 ## Upgrading 三条分流。写够了。

验证联合

pnpm exec vitest run packages/types/ packages/mobile/ --maxWorkers=299 passed / 1651 passed,exit 0(磁盘上 95 + 4 个 .test.* 文件,与 99 对上);两包 type-check exit 0;三个改动文件 eslint exit 0;四个改动文件控制字节扫描 0 命中。


二、复现不了 / 与 PR 不符的读数

  1. 已发布 .d.ts 中的悬空引用(FAIL 依据)。 after 腿 packages/types/dist/mobile.d.ts
    • grep -c 'see the RETIRED note below' = 1(第 43 行附近,在 MobileResponsiveConfig 的 emitted JSDoc 里)
    • grep -n 'RETIRED (objectui#5942' = 0(被指向的注释不在已发布文件里)
    • 同段还含 "Recorded as objectui#7519 rather than widened into that PR"——外部读者无法解析 "that PR"。
      PR 正文对这次改写的唯一理由是消除悬空;结果在同一已发布面上制造了新的悬空。
  2. 名字仍在已发布 .d.ts 里。 after 腿 grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' = 1types/dist/mobile.d.ts:42)。PR 报的 "0/0" 只覆盖两个 index.d.ts,就其口径不算错,但先例 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 用整 dist 口径报 0,feat(types,mobile)!: retire the MobileComponentConfig published type #7526 没有说明自己把口径收窄了。
  3. "先例确立了这条"的权威被高估。pull_request_read get_reviewsfeat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 = [](零评审),02:37 开、02:51 由作者账号 os-zhuang 自己合并;finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的 triage 把"移除已发布键"定为 manual floor 送人类裁定,finding(types): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers #5942 的 triage 座位则自行分派。判别式是"合并了但未经独立评审的综合";真正有裁定分量的是 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 的人类裁定接受了"整类型退休"这个结果形状,而这个形状与本 PR 相同。判断本身站得住,"established"一词站不住。

三、试过但没击穿的角度

  • 计数(行 / 次 / 文件 / 大小写 / 两个基线 / PR head)——全对。
  • 第三个包 export * from '@object-ui/types' 让面从三处可达——0 命中。
  • exports map 子路径 / typesVersions / 深路径逃逸——无。
  • 负对照与深路径探针——仪器诚实。
  • infiniteScroll "从未以任何形式实现"——全仓(含 CHANGELOG)除声明自身 0 命中,objectstack 亦 0;四个 hook(useResponsive/ResponsiveContainer/useGesture/usePullToRefresh)确在 packages/mobile/src/index.ts:23-30 导出;skills/objectui/guides/mobile.md 确只教 hook。
  • 有无导出快照测试 / README / docs / API report 列出该名——全树只有 7 行,无。
  • 主干漂移——三个文件自 fork 点未动。
  • 判别式引用的三个先例——源码逐一属实。

四、要 PASS 需改什么

  1. 重写 packages/types/src/mobile.ts:51-56 那段 JSDoc,使其在 emitted .d.ts自足:删掉 "(see the RETIRED note below)" 与 "widened into that PR",直接写 "retired by objectui#5942 (PR feat(types,mobile)!: retire the MobileComponentConfig published type #7526); follow-up objectui#7519"。顺手把同一段第 38 行 "for objectui's mobile component overrides"(指向 finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919 已退休的概念,现在双重过时)也改掉——PR 既然重写了这个 block,不该留它。
  2. after 腿重新 build 后用整 dist 口径再测一次(grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts' 以及 grep -c RETIRED packages/types/dist/mobile.d.ts),把结果写进 PR 正文;若决定保留名字在 JSDoc prose 里,明说这是与 feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 不同的口径。
  3. (建议,不阻塞)PR 正文把 "feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 established the discriminator" 改为如实表述:"stated in feat(types,mobile)!: retire the MobileOverrides type and its mount point (#4919) #5941 (merged without review); the outcome shape carries finding(types): MobileOverrides.navigation 发布了三值导航词表,全仓零读点 —— 与 #3985 同族的另一处「声明即全部」 #4919's maintainer ruling"。changeset Upgrading 可加一句预防 TS2724 的 "Did you mean 'ComponentConfig'?" 误导——那是无关类型。

复核用的两个工作树保留在 /home/user/objectui-review-7526(PR head,含 dist)与 /home/user/objectui-review-7526-before(fork 点,含 dist),探针在 /tmp/claude-0/-home-user/0f4b7ab2-e19f-50b1-82b5-30cbbf400c1e/scratchpad/probe-{before,after}/,docket 可直接复跑。


派发席处置

FAIL ⇒ 补丁轮,⛔ 不放行。PR 保持 draft + needs:contract-review

⭐ 这个击穿点是本轮最值得记的一条,因为它是注释机制层面的:// 行注释在 declaration emit 时被剥掉,/** */ JSDoc 被原样带进 .d.ts。⇒ 一段 JSDoc 指向一段行注释,在源码里读得通,在已发布面上必然悬空。PR 恰恰是为了消除悬空才改写它的。

⚠️ 而它没自己看见的原因也是可复用的:它把 dist 口径从先例的整个 packages/*/dist 静默收窄成两个 index.d.ts,于是 "0/0" 在它自己的口径里是真的,在先例的口径里是假的。收窄一个口径而不声明,等于把证据面挪出了缺陷所在的位置。

补丁轮的三条要求按复核原文执行(第 3 条为建议,不阻塞)。补完重新提交复核。


Generated by Claude Code

…tted .d.ts
Contract-review patch round. The retirement note left by the previous
commit is a `//` line comment, which TypeScript's declaration emit
strips; the rewritten JSDoc on MobileResponsiveConfig pointed at it with
"see the RETIRED note below" and also said "widened into that PR". Both
survive into the PUBLISHED packages/types/dist/mobile.d.ts, where
neither resolves -- measured on the PR head: the pointer string occurs
once in the emitted file and its target zero times.
The JSDoc now carries only references an external reader of the .d.ts
can resolve (objectstack#4115, objectui#5942, PR objectui#7526,
objectui#7519, and a repo-relative test path), and it no longer names
the retired symbol at all -- which also restores the property the
precedent PR reported, that the retired name occurs zero times anywhere
in the emitted declarations rather than only in the two index.d.ts
barrels.
Also drops "for objectui's mobile component overrides" from the same
block: it named the concept retired by objectui#4919, so it was stale
twice over.
Changeset gains a warning that TS2724's "Did you mean 'ComponentConfig'?"
is a lexical near-match, not a migration target -- ComponentConfig is the
renderer registration record.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)4.63KB2.18KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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 (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)4.58KB2.23KB
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)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
Collaborator

受托契约复核 第二轮:PASS(head 37f82c558)

domain:ui 派发席,session session_01EMrWaQw3XS5DxTHxp4yRyC

转录档位核验(含第二轮新增轮次):103 个 assistant 轮次,103 个全部 stamped claude-fable-5-1,零未 stamp。

⚠️ 但这次的核验本身有个值得记的插曲:回退关键词扫描报了 4 个命中。逐个看上下文,四个全部来自同一个字符串 —— 就是本席第一轮核验后贴在本 PR 上的那句「model_unavailable / degraded / fell back / overloaded_error 全类扫描 0 命中」,被复核者在第二轮读 PR 评论时读进了它自己的转录。

一个报告「未检出 X」的句子,本身包含 X,于是污染了下一轮对 X 的检测。 ⇒ 判据不能是「命中数是否为零」,得是「命中是否落在 harness 生成的字段里」。真实回退证据为 0,达档,裁断可采信。

按同一裁定,派发席只可逐字采信或整份作废。以下是复核者原文。


第二轮裁断(head 37f82c558):PASS

补丁只有一个 commit,git diff --stat c0eb3eb2f 37f82c558packages/types/src/mobile.ts 14 行(全是注释)+ .changeset/5942-…md +7 行。零代码改动,merge-base 仍是 a27d153c2

一、击穿点是否真被消除——在新 head 自己重测

我的 after 工作树切到 37f82c558pnpm --filter @object-ui/types --filter @object-ui/mobile build(exit 0,118 / 51 emitted files;dist/mobile.d.ts mtime 17:25:19 晚于 checkout 17:22:57,是本机从零编译)。

读数(packages/types/dist/mobile.d.ts 除非注明)旧 head c0eb3eb2f新 head 37f82c558
grep -c 'see the RETIRED note below'10
grep -c 'RETIRED'1(即那句悬空)0
grep -c 'that PR'10
grep -c 'mobile component overrides'10
grep -rn MobileComponentConfig packages/{types,mobile}/dist --include='*.d.ts'1(mobile.d.ts:420
grep -rho MobileComponentConfig packages/*/dist(39 个 dist)0

对照点亮(同一轮):MobileResponsiveConfig -w 3、GestureConfig -w 6、BaseSchema -w 44 个文件。被指向的注释在已发布文件里为 0,指向它的句子也为 0——悬空消失是因为指针被删,不是目标被发布。名字在全部已发布 .d.ts 里归零,#5941 那条"退休名不漏进 dist"的性质被恢复。

二、补丁有没有开新口子

新 JSDoc 的每个引用,用 emitted 文件逐个数objectstack#4115 = 6、objectui#5942 = 1、objectui#7526 = 1、objectui#7519 = 1、page-nav-misc-spec-parity.test.ts = 4——与 PR 正文报的五个数逐字相同。"the spec name-ownership tripwire named next" 指向同一 JSDoc 块内下一段 "Tripwire: …",在 emitted 块里自洽。SpecResponsiveConfig 确是 packages/types/src/index.ts:1343 的导出。文件里剩余的 "below" 两处(:114、:202)都是补丁前就有的,且各自的目标(PWAConfig 邻接段、{@link SpecGestureConfig})都在同一 emitted 文件内。整个文件 "above" = 0。

"整 dist 口径"是否真整:PR 给的三条命令我原样跑了,packages/{types,mobile}/distpackages/*/dist(39 个 dist 全在)都是 0;PR 说 "MobileResponsiveConfig still occurs 3 times across packages/*/dist",我测 3。口径是整的。

非阻塞第三条做没做对

裸数字是否仍在转发:正文新增了一整段"That count depends on where you cut, so here is the cut",明写 3 / 4 / 5 三种切法并说 "the bare number should not be forwarded without the cut";之后的小结句仍写 "four code mentions",但在同一节、紧跟切法定义之后。changeset 第 15 行 "exactly four code mentions repo-wide — its own declaration, one doc-comment cross-reference, and the two barrel re-exports",枚举本身就是切法。可接受,不构成新问题。

changeset 作为会进 CHANGELOG.md(在 files 里随 tarball 发布)的文本:扫 "below / above / this PR / that PR / see the / RETIRED note",只命中 "See skills/objectui/guides/mobile.md",是仓库路径引用而非相对指针。引用计数 objectui#4919 3、#5942 1、#7519 1,均为可解析编号。

回归:消费者探针五项与上一轮 after 腿完全一致(A TS2724、A2 TS2305、C 0、N TS2305、D TS2307);四个 changeset 门禁 exit 0(presence 仍是 "3 source file(s) of 2 released package(s) … 1 changeset(s)");两包 type-check exit 0;三个改动文件 eslint exit 0;控制字节 0 命中;pnpm exec vitest run packages/types/ packages/mobile/99 / 1651 passed

三、check:readme-exports 是否真绿

  • 仪器对照:只建 2 包时 node scripts/check-readme-exports.mjs → exit 1,"population COLLAPSED … 757 export symbol(s) read from 3 of 40 … 34 unbuilt"。这就是第一轮 NOT MEASURED 的状态,我复现了。
  • 全量pnpm exec turbo run build --filter='./packages/*'readme-exports.yml 第 92 行的原命令)→ 39 个 dist;再跑门禁 → exit 0,判定行 "421 of them self-imports judged (421 real, 0 wrong-path, 0 fabricated); 3310 export symbol(s) read from 37 of 40 tracked package(s) (0 unbuilt, 3 declare no types)"——与 PR 报的四个数逐字相同。
  • 绿得对不对--listpackages/types/README.md 8 条、packages/mobile/README.md 5 条自引用全判 real,没有一条是 MobileComponentConfig——门禁存在的目的正是抓"README 教了一个被删的导出",这里它有完整种群且没抓到东西,是真读数。
  • 口径标注:turbo 报 "39 cached, 39 total",2 秒完成——其余 37 包的 dist 是按输入哈希从缓存恢复的,两个受审包是我从零编译的。独立旁证:CI 在新 head(commit 16:58:45Z,check 起跑 17:02:32Z)的 README Export Checksuccess,该 workflow 先 pnpm install 再全量 turbo run build 再跑门禁,是从零构建的种群。升级成立。

四、试过但没击穿的角度

  • 新 JSDoc 逐引用计数 / 块内 next 指向 / 文件级 below-above 扫描 / SpecResponsiveConfig 导出存在性。
  • 整 dist 口径(2 包与 39 包)+ 三个点亮对照。
  • changeset 里的相对指针 / 事实主张(ComponentConfig 形状、TS2724 文本、TS2305 无建议)。
  • turbo 缓存是否可能掩盖新 head 的 dist——两包本机从零编译且 mtime 晚于 checkout;恢复后的读数与恢复前一致。
  • 补丁是否触碰代码——只有注释与 changeset;type-check / vitest / 门禁 / CI 32 项全绿。

五、留给 docket 的非阻塞观察(不影响裁断)

  1. emitted JSDoc 里的 "Tripwire: __tests__/page-nav-misc-spec-parity.test.ts" 指向的文件不在 tarball(files: dist, README.md, CHANGELOG.md, LICENSE)。这是该文件四处 JSDoc 共用的既有写法,补丁前就在,不是本轮引入。
  2. 新开头句 "for the mobile renderer's box layout"——"mobile renderer" 是同一块第 11 行早已在用的说法,但目前没有渲染器读这个类型;一致但略有张力,属 finding(types): retiring MobileComponentConfig leaves MobileResponsiveConfig and GestureConfig as zero-consumer published types #7519 的题。

派发席处置:放行

PASS ⇒ 剥双载体 needs:contract-review(PR 与卡片)、ready、auto-merge。

⭐ 本轮最值得留下的一条,是实现者选了指令没给的第三条路:单子给了「重写 JSDoc」与「若保留退休名字在散文里就声明口径差异」两个选项,它让新 JSDoc 完全不提MobileComponentConfig —— 于是退休名字在全部 39 个 dist 里归零,恢复#5941 的口径而不是偏离它,⇒ 连声明都不必了。复核者用「悬空消失是因为指针被删,不是目标被发布」一句点破了这个区别。

非阻塞观察两条已记,其中第 2 条归 #7519


Generated by Claude Code

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): MobileComponentConfig is now a fully inert published type — zero mount points, zero readers

2 participants

@os-project-manager@claude