Skip to content

chore(providers): drop the unused @objectstack/spec dependency - #6048

Merged
yinlianghui-tw merged 1 commit into
mainfrom
claude/issue-5753-providers-drop-spec-dep
Aug 24, 2026
Merged

chore(providers): drop the unused @objectstack/spec dependency#6048
yinlianghui-tw merged 1 commit into
mainfrom
claude/issue-5753-providers-drop-spec-dep

Conversation

@yinlianghui-tw

@yinlianghui-tw yinlianghui-tw commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes #5753

@object-ui/providers declared @objectstack/spec in dependencies and imported it
nowhere. This drops the declaration and corrects the now-inaccurate prose comment in
packages/providers/tsconfig.test.json, per the unlock comment and the PM dispatch order
(#5753#issuecomment-5396525948) — both of which supersede the body's proposal to defer
this to the spec-pin refresh chain.

Premise re-verification — the whole justification is "zero import sites"

Measured on origin/main at ad0f5f11f, before removing anything. The card's original
measurement was taken on a branch that has since merged, so it had to be re-taken.

The instrument — import-shaped only (from / require( / import( / vi.mock( /
jest.mock(), matching the bare name and every subpath (/ui, /data, …), and covering
type-only imports because import type … from '…' matches the same anchor:

grep -rnE "(from|require\(|import\(|vi\.mock\(|jest\.mock\()[[:space:]]*['\"]@objectstack/spec" \
  TREE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.turbo
TREE Hits
packages/providers/ 0
packages/ + apps/ (positive control, same command) 434

An empty grep is not a reading on its own. The identical invocation returning 434 hits one
directory level up proves the instrument works and the zero is a real absence. Two narrower
controls agree: @objectstack/spec/data import sites across packages/ + apps/ = 90, and
a plain substring objectstack/spec across the same trees = 2381.

Beyond src/, checked and clear: both test files, tsconfig.json, tsconfig.test.json,
side-effect imports (bare import '…'), triple-slash reference-types directives,
declare module, and tsconfig types arrays. packages/providers has no vitest.config.ts
or build config of its own (build is bare tsc). Every remaining mention of the string in
the package:

  • package.json:34 — the declaration, removed here.
  • tsconfig.test.json:13 — prose, corrected here.
  • CHANGELOG.md ×4 — immutable release history, deliberately untouched.

The only imports left in the package are react, @object-ui/types, relative paths, and
vitest / @testing-library/react in tests.

⚠️ What check-phantom-dependencies.mjs does and does not prove here

The card flagged that this gate guards the inverse direction — import without a
declaration — and that is correct in one important sense: it never could have reddened on
the dead edge, and its green does not validate that the dependency was unused.
A
declared-but-unimported dependency is invisible to it by construction, which is exactly why
this defect needed a human-filed finding rather than a gate.

In the other direction it is load-bearing, and worth stating precisely because it covers a
gap that type-check does not: after the declaration is gone, any surviving import site
becomes a phantom dependency, which is what this gate is built to catch.

That distinction matters here because type-check alone is not proof of absence. The
root package.json declares @objectstack/spec (line 89), so pnpm places it in the
workspace-root node_modules, and Node's/TypeScript's upward walk reaches it from any
package directory. Measured after pnpm install with the declaration removed:

$ cd packages/providers && node -e "require.resolve('@objectstack/spec', {paths:[process.cwd()]})"
resolved: …/node_modules/.pnpm/@objectstack+spec@17.2.0…/node_modules/@objectstack/spec/dist/index.js

So a stray spec import in this package would still typecheck and still build green. This is
the trap check-phantom-dependencies.mjs's own header documents (objectui#4394): "a
dependency-direction check performed BY RESOLUTION returns the wrong answer, and returns it
confidently." The grep above and the phantom-deps gate are the two readings that actually
bear weight; the green type-check is corroboration, not proof.

Gates — verdict lines as each gate printed them

Run at 89c03822c (the final commit; working tree clean). Exit codes captured before any
pipe.

Gate Exit Verdict line
pnpm install 0 Done in 17.2s using pnpm v10.31.0 — resolves cleanly with the entry gone; lockfile diff is exactly the one removed importer entry
pnpm --filter '@object-ui/providers^...' build 0 dependency closure (@object-ui/types) built first, so the checks below read a fresh .d.ts
pnpm --filter '@object-ui/providers' run type-check 0 tsc --noEmit && tsc -p tsconfig.test.json — both projects, no output
pnpm --filter '@object-ui/providers' run build 0 tsc
pnpm exec vitest run packages/providers/ 0 Test Files 2 passed (2) · Tests 7 passed (7)
pnpm --filter '@object-ui/providers' run lint 0 ✖ 15 problems (0 errors, 15 warnings) — all pre-existing in src/, which this PR does not touch
node scripts/check-phantom-dependencies.mjs 0 ✅ Every in-scope import is declared by the package that publishes it.
node scripts/check-changeset-presence.mjs 0 ✅ No source of a released package changed in this range, so no changeset is owed. (1 changeset added anyway — see below)
node scripts/check-changeset-fixed.mjs 0 ✅ All workspace packages are in the changeset fixed group.
node scripts/check-changeset-no-major.mjs 0 ✅ No changeset declares a major bump.
node scripts/check-control-bytes.mjs 0 ✅ check-control-bytes: OK (scanned 4990 tracked text file(s); skipped 85 binary).
node scripts/check-package-self-import.mjs 0 ✅ No package names itself inside its own src/.
node scripts/check-lint-coverage.mjs 0 ✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).
node scripts/check-type-check-coverage.mjs 0 ✅ type-check coverage: 45/46 via type-check … 41/41 packages compile their tests

Two notes on how the suite was run, both deliberate:

  • The dispatch order named pnpm --filter '@object-ui/providers' run test. This repo's own
    guard refuses that invocation (vitest 调用被拒绝, objectui#3378): a package-directory
    vitest root silently runs apps/console's 22 files and reports them as green while running
    none of this package's. Re-run from the repo root as the guard instructs, which is what the
    passing 2 files / 7 tests above reports.
  • node scripts/check-published-dist-tooling.mjs was not run locally — a declared
    narrowing. It rebuilds every package and exceeds this container's foreground limit; CI owns
    it. It is also unreachable by this diff, which touches no src/, no build script and no
    tsconfig the build uses (tsconfig.test.json is noEmit). packages/providers/dist/ was
    inspected directly and carries no tooling material.

Published surface

Built and inspected: dist/types.d.ts imports only react and @object-ui/types; no
emitted .d.ts or .js references a spec symbol. The four objectstack strings surviving
in dist/ are prose in doc comments (@objectstack/service-storage in an adapter's TSDoc,
two issue references) plus one adapter name string literal — no module specifier among them.

Changeset

check-changeset-presence.mjs says none is owed (it scores src/ changes, and this diff has
none). Added one anyway, scored patch, because the honest question is what consumers
install and that genuinely changes: the published manifest loses a dependencies entry.

patch, not minor: there is no API or behaviour change, and no supported access goes away.
Consumers on an isolated node_modules never had legitimate reach-through to
@objectstack/spec via this package — any consumer that did import it was relying on flat-
install hoisting, which was never a contract. The install graph is the only thing that moves.
For contrast, the change that added this edge was scored minor because it changed the
public .d.ts; nothing in this PR touches a type.

Scope

Exactly the two files the card names, plus the changeset and the lockfile line pnpm install
regenerated. No other package's spec declaration is touched — the range question for packages
whose published d.ts references spec-17.1.0-only symbols is #5793's, a separate
un-dispatched card.

(Body edited once after creation: GitHub's body sanitizer had eaten two short
angle-bracket fragments — the TREE placeholder in the grep block and the triple-slash
directive name. Same content, spelled without angle brackets.)


Generated by Claude Code

`@object-ui/providers` declared `@objectstack/spec` but imported it nowhere.
The edge was promoted from `devDependencies` to `dependencies` when
`ThemePreference` was derived from the spec's `ThemeMode` union; objectui#5716
re-pointed that derivation at `@object-ui/types` and removed the last three
import sites, leaving the declaration with no reader.

Re-measured on origin/main at ad0f5f1 with a positive control: the
import-shaped grep returns 0 hits under packages/providers/ and 434 across
packages/ + apps/. The stale `tsconfig.test.json` comment that named the spec
among the specifiers its empty `paths` re-routes is corrected in the same
stroke.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3219.5 KB 3990.2 KB
Main entry chunk (gzip) 153.6 KB 350 KB
Entry file index-ghKAWwtD.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 10.38KB 3.90KB
app-shell (runtime-config.js) 18.10KB 6.51KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 505.22KB 114.56KB
core (index.js) 4.92KB 1.97KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 165.30KB 45.79KB
fields (index.js) 238.40KB 59.89KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 23.13KB 7.63KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.53KB 3.38KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.64KB 1.50KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.93KB 0.88KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.66KB 18.32KB
plugin-chatbot (index.js) 188.21KB 44.67KB
plugin-dashboard (index.js) 133.35KB 34.44KB
plugin-designer (index.js) 212.30KB 42.80KB
plugin-detail (index.js) 244.12KB 61.87KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 125.63KB 30.64KB
plugin-gantt (index.js) 164.15KB 39.88KB
plugin-grid (index.js) 200.79KB 54.26KB
plugin-kanban (index.js) 52.93KB 14.60KB
plugin-list (index.js) 111.86KB 27.22KB
plugin-map (index.js) 20.11KB 6.64KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.49KB 11.93KB
plugin-timeline (index.js) 26.49KB 7.59KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.57KB 20.74KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 52.40KB 17.45KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.35KB 0.70KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 12.13KB 3.65KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 7.54KB 2.63KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.49KB 2.14KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

Copy link
Copy Markdown
Collaborator Author

PM: ACCEPT — and two corrections to my own dispatch order

Scope verified: 4 files (package.json, tsconfig.test.json, the changeset, the lockfile line), +32 / −8. No other package's spec declaration touched, so #5793's range question stays intact for its own card.

⚠️ Correction 1 — my order named type-check as the gate that would catch a missed import. That was wrong, and the dev proved it.

I wrote: "pnpm --filter '@object-ui/providers' run type-check — clean. This is the one that would catch a missed type-only import."

It is not. The root package.json declares @objectstack/spec (line 89), so pnpm places it in the workspace-root node_modules and the upward resolution walk reaches it from any package directory. Measured after removing the declaration and reinstalling:

$ cd packages/providers && node -e "require.resolve('@objectstack/spec', {paths:[process.cwd()]})"
resolved: …/node_modules/.pnpm/@objectstack+spec@17.2.0…/node_modules/@objectstack/spec/dist/index.js

So a stray spec import in this package would still typecheck and still build green. Had the grep been sloppy and my named gate been trusted, this PR would have shipped a phantom dependency behind a confident green. The dev not only avoided that, it identified the correct instrument: check-phantom-dependencies.mjs, which is precisely the gate that reddens on a surviving import site once the declaration is gone.

That is also this repo's own documented trap — check-phantom-dependencies.mjs's header (objectui#4394): "a dependency-direction check performed BY RESOLUTION returns the wrong answer, and returns it confidently." My order walked into the exact hazard the file warns about. The load-bearing readings here are the grep and the phantom-deps gate; the green type-check is corroboration, not proof, and the PR body says so.

⚠️ Correction 2 — my order named a vitest invocation this repo refuses

I wrote pnpm --filter '@object-ui/providers' run test. The repo's own guard refuses it (objectui#3378): from a package directory, vitest's root resolution silently runs apps/console's 22 files and reports them green while running none of this package's. Following my order literally would have produced a green that measured nothing — a wrong-green, not a missing check.

The dev re-ran from the repo root as the guard instructs: Test Files 2 passed (2) / Tests 7 passed (7). Correct call. I am carrying this into the seat's dispatch template so no future order on this lane names the refused form.

What earns the accept on its own merits

  • The premise was re-measured before anything was removed, on origin/main @ ad0f5f11f, with the positive control the order required: the identical command returning 0 hits under packages/providers/ and 434 one directory up. That is what makes the zero a reading rather than a broken instrument.
  • The instrument was built for the actual question — import-shaped anchors (from / require( / import( / vi.mock( / jest.mock() matching the bare name and every subpath, with type-only imports covered because import type … from hits the same anchor. Then the long tail was checked explicitly: side-effect imports, triple-slash reference directives, declare module, tsconfig types arrays, both test files, and the absence of a package-local vitest.config.ts.
  • The published surface was inspected, not assumed. dist/types.d.ts imports only react and @object-ui/types; the four surviving objectstack strings in dist/ are doc-comment prose and one adapter name literal — no module specifier among them.
  • The changeset judgment is right, and right for the stated reason. The gate said none was owed (it scores src/); one was added anyway at patch, because the honest question is what consumers install and the published manifest genuinely loses a dependencies entry. patch rather than minor is correctly argued: no API or behaviour change, and reach-through to @objectstack/spec via this package was never a contract — only flat-install hoisting, which is not one.
  • Both narrowings were declared rather than hidden: the refused vitest form above, and check-published-dist-tooling.mjs not run locally (rebuilds every package, exceeds the container's foreground cap, unreachable by a diff with no src/ and no build-path tsconfig) with dist/ inspected directly instead.

Landing

⏳ CI still converging on 89c03822c — seven checks in progress including all four shards. Not turned ready yet; a self check-in is armed to verify every-check-green and land it. mergeable_state is behind, noted for the landing step.


Generated by Claude Code

@yinlianghui-tw
yinlianghui-tw marked this pull request as ready for review August 24, 2026 14:54
@yinlianghui-tw
yinlianghui-tw added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit a1c41c5 Aug 24, 2026
23 checks passed
@yinlianghui-tw
yinlianghui-tw deleted the claude/issue-5753-providers-drop-spec-dep branch August 24, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: @object-ui/providers declares an @objectstack/spec dependency that nothing in the package imports once #5716 lands

2 participants