Skip to content

fix(build): wire both shared vite-plugin-dts factories into every dts package, and ratchet them there - #6201

Merged
yinlianghui-tw merged 2 commits into
mainfrom
claude/issue-5483-dts-config-wiring
Aug 25, 2026
Merged

fix(build): wire both shared vite-plugin-dts factories into every dts package, and ratchet them there#6201
yinlianghui-tw merged 2 commits into
mainfrom
claude/issue-5483-dts-config-wiring

Conversation

@yinlianghui-tw

Copy link
Copy Markdown
Collaborator

Fixes#5483
Fixes#5439

Folded dispatch, chain head #5483. Two commits, two acceptance criteria, and one ratchet over the population both cards describe — which is the reason they were folded rather than run serially. Run serially this would have produced two near-duplicate gates over one population, each checking one module.


Population, re-derived on today's main (17ccec977)

Both cards' populations were stale — #5483's from 2026-08-21, #5439's from 2026-08-20, and #5439's said so about itself. Re-derived from the tree:

  • 22vite.config.ts files call dts(components, fields, layout, and 19 plugin-*.
  • createDtsFailOnTypeErrors was wired into 1 of 22 (layout). Now 21 of 22.
  • createDtsExplicitExtensions was wired into 2 of 22 (components, layout). Now 22 of 22.

#5439's 13-package table is not the population. The six it listed as "outside the closure built here and are untested, not clean" were all measured here and all needed wiring: plugin-gantt 17, plugin-timeline 4, plugin-ai 3, plugin-markdown 3, plugin-map 1, plugin-tree 1 extensionless specifiers. plugin-detail measured 82, not 81.

The one package that stays unwired, and why

packages/fields keeps its tsc && prefix and does not spread createDtsFailOnTypeErrors. Its build script is tsc && vite build && node scripts/build-css.mjs; the leading tsc exits non-zero on the same diagnostics beforevite build runs, so a dts-leg exit code could never be what decides that build. The prefix is load-bearing, not redundant next to the 21 wired call sites, and the call site now says so.

The card asked for that as a comment at the build script. package.json cannot hold a comment and this repo has no "//"-key convention to borrow (zero occurrences repo-wide), so the note lives at the dts() call in packages/fields/vite.config.ts and names the build script — and the claim is machine-checked: the ratchet's exclusion entry re-derives the tsc && prefix from packages/fields/package.json. Drop the prefix and the gate reds, demanding the factory instead. That is strictly stronger than prose, which is why it was done this way rather than inventing a JSON comment convention across 39 manifests.

fieldsdoes get createDtsExplicitExtensions — measured 0 extensionless both before and after, because its sources already write explicit .js specifiers, so wiring it changes nothing today and stops it drifting back.


#5439 is not "#5365 again" — this is the whole severity argument

A PR body that describes this as "wire the extensions module into more packages" loses the reason it matters.

Proven on @object-ui/plugin-chatbot in #5439 by compiling the same built dist twice against a probe (generics spaced to survive GitHub's body sanitizer):

type IsAny< T > = 0 extends 1 & T ? true : false;
const assertAny: IsAny< typeof useHitlInChat > = true;
  • moduleResolution: nodenext — compiles clean, i.e. the symbol isany.
  • moduleResolution: bundlerTS2322, i.e. properly typed.

That is the worse of the two failure modes: it opens quietly, the moment a consumer pins nodenext, and it is what produces the 21 residual TS7006 on @object-ui/app-shell reported in #5365's PR.


Measurements

#5483 — blast radius, re-measured on today's main: zero

turbo run build --filter=!@object-ui/site --concurrency=2 --continue with the factory wired into 21 call sites: 43/43 tasks successful, 4m12s. None of the 22 dts packages was a cache hit (verified against the run's cache hit / cache miss lines — the 14 hits are types, core, react, i18n, mobile, permissions, providers, auth, collaboration, sdui-parser, data-objectstack, react-runtime, create-plugin, object-ui), so all 22 built fresh. Wiring turns nothing green red.

#5439 — per package, built with the module wired before the final diff

vite-dts-explicit-extensionsthrows on any specifier it cannot resolve, so this half could have redded a currently-green build. It did not: same full build with both modules wired, 43/43 successful, 3m45s, zero throws. No package had to be left unwired, so there is no finding of that kind to report.

Extensionless relative specifiers in emitted dist/**/*.d.ts (AST-located module specifiers, so JSDoc prose is excluded — the same criterion #5439 used), before → after:

packagebeforeafterpackagebeforeafter
plugin-detail820plugin-list160
plugin-designer620plugin-report140
plugin-chatbot530plugin-charts90
plugin-grid360plugin-calendar50
plugin-dashboard310plugin-timeline40
plugin-form310plugin-ai30
plugin-kanban220plugin-markdown30
plugin-view190plugin-editor10
plugin-gantt170plugin-map10
plugin-tree10

410 → 0.components (119 relative specifiers), layout (8) and fields (155) were already 0 and stay 0. The before column is a real rebuild, not an inference: the spread was reverted with git checkout HEAD~1 -- after the fix was committed, the tree rebuilt, and the count re-taken; the restore was proven with an empty git diff HEAD.

Verification here is deliberately not "it still compiles" — named re-exports yield a silent any, so compiling proves nothing. It is a direct count driven to zero.


The ratchet — scripts/__tests__/vite-dts-wiring-ratchet.test.ts

One AST walk over every vite.config.* under packages/, apps/, examples/ that calls dts(, asserting both spreads. Verdict line carries the count:

vite-plugin-dts wiring ratchet — 22 dts( call sites of 26 vite configs
✓ finds a real population: 22 call sites, floor 15
✓ every dts( call site spreads createDtsExplicitExtensions
✓ every call site that spreads createDtsExplicitExtensions imports it from scripts/vite-dts-explicit-extensions.ts
✓ every dts( call site spreads createDtsFailOnTypeErrors
✓ every call site that spreads createDtsFailOnTypeErrors imports it from scripts/vite-dts-fail-on-type-errors.ts
✓ exclusions stay capped, and each one names a live call site
✓ every exclusion re-derives its stated reason from the tree

Detection is by AST, not substring: toContain is satisfied by the factory's name appearing in a comment, which is the exact shape of a call site someone disabled and explained. dts must also be the local name bound to the vite-plugin-dts default import, so a same-named local helper cannot join the population by coincidence.

Non-vacuity, each direction predicted before it was run

  1. Positive control, extensions module — dropped ...createDtsExplicitExtensions(...) from packages/layout/vite.config.ts, leaving its import in place so the miss is the spread and not the import. Predicted: exactly one test red, naming the file and the module. Observed exactly that — 1 vite-plugin-dts call site(s) do not spread createDtsExplicitExtensions… - packages/layout/vite.config.ts, 1 failed | 6 passed.
  2. Positive control, fail-on-error module — dropped ...createDtsFailOnTypeErrors(...) from packages/plugin-grid/vite.config.ts, leaving the other factory's spread in place so the control is single-module. Observed: 1 failed | 6 passed, naming packages/plugin-grid/vite.config.ts and that module. A gate that only noticed one would be half a gate.
  3. Population non-vacuity — pointed the walk at a directory that does not exist. Predicted: red, not green. Observed 2 failed | 5 passedexpected 0 to be greater than or equal to 15, plus the stale-exclusion guard. The 5 that still passed are precisely the vacuously-satisfiable ones, which is why the floor assertion exists.
  4. Exclusion re-derivation — removed the tsc && prefix from packages/fields's build script. Observed: red, The reason packages/fields/vite.config.ts is exempt from createDtsFailOnTypeErrors no longer holds.

Every mutation was proven on disk by anchored counts of the injected and the removed text — never an editor's exit code — and every restore by an empty git diff HEAD.

End-to-end for #5483

One deliberate TS2322 appended to packages/plugin-tree/src/index.tsx, exit code read from ${PIPESTATUS[0]} as the card did, same injection both legs:

legpackages/plugin-tree/vite.config.tspnpm --filter @object-ui/plugin-tree build
Areverted to origin/main (unwired)exit 0, with TS2322 printed in the log
Bas this branch ships it (wired)exit 1, Error: [dts-fail-on-type-errors] the declaration build … reported 1 type error, so it must not exit 0: src/index.tsx(49,14): error TS2322

Restored, proven with an empty git diff HEAD.


Gates run locally, on final head beeb94058

Gate list derived from package.json and .github/workflows/, not from the dispatch. Exit codes captured by redirect before any pipe.

gateexit
pnpm exec vitest run scripts/ (root vitest, objectui#3378)0 — 72 files, 1967 tests passed
pnpm lint0 — 47/47 tasks, 0 cached (full repo, not narrowed)
pnpm lint:root0 — 0 errors, 28 pre-existing warnings
pnpm type-check0 — 81/81 tasks
pnpm type-check:scripts / :coverage / pnpm lint:coverage0 / 0 / 0
pnpm check:esm-specifiers, check:node-esm-load, check:published-dist0
pnpm check:phantom-deps, check:self-import, check:entry-guard0
pnpm check:spec-symbols, check:action-forward-parity, check:designer-field-key-parity, check:icon-record-names0
pnpm check:i18n-keys, check:i18n-drift, check:skills-paths0
node scripts/check-control-bytes.mjs0 — 5115 files scanned
check-changeset-presence / -no-major / -fixed0 / 0 / 0

Heavy runs were serialised through the container's shared verify lock.

Changeset

A real one, not skip-changeset: this changes published typings. check-changeset-presence says none is owed (no published src/ changed — only build configs), which is exactly the gap worth writing through by hand. Bumps are patch; per AGENTS.md no changeset in this repo declares major.

Downstream TS2305 / TS7006 surfacing in consumers is the fix working, not a regression: those call sites were being handed a silent any.

Scope

The vite.config.ts files of both sets, fields's one clarifying comment, the ratchet and its test, and the changeset. Neither shared module's behaviour was touched — both are landed and pinned by existing call sites, and their own unit tests (scripts/__tests__/vite-dts-*.test.ts) are unchanged and green. No pre-existing type error was fixed; none surfaced.

Generated by Claude Code


Generated by Claude Code

…kages
`scripts/vite-dts-fail-on-type-errors.ts` landed with #5370 / PR #5482 and was
spread into `packages/layout` only, because that PR's declared file surface was
the shared module plus that one package. The other 20 `vite-plugin-dts` packages
kept printing a type error from the declaration program and exiting 0.
Spread `createDtsFailOnTypeErrors({ packageDir: __dirname })` into all 20, in
the form pinned by `packages/layout/vite.config.ts`.
`packages/fields` is deliberately left unwired and now says so at the call site:
its build script is `tsc && vite build && node scripts/build-css.mjs`, and the
leading `tsc` exits non-zero before `vite build` ever runs — so a dts-leg exit
code could not be what decides that build. The comment records that the prefix
is load-bearing rather than redundant next to the 21 wired call sites.
Part of #5483
…typings
`scripts/vite-dts-explicit-extensions.ts` landed with #5365 and was wired into
`packages/components` and `packages/layout` only, because that dispatch was
fenced to those two. Every other `vite-plugin-dts` package kept shipping
extensionless relative specifiers in its emitted `dist/**/*.d.ts` — 410 of them,
measured on this tree.
This is not "#5365 again". Those two packages re-export mostly through
`export * from './ui'`, and a star re-export from a module the compiler cannot
resolve contributes NO names — the consumer gets a loud TS2305. These packages
re-export mostly through NAMED re-exports, so TypeScript still declares the name
and merely fails to type it: the consumer gets no error at all and a silent
`any`. It is the quieter and worse of the two failure modes, and it opens the
moment a consumer pins `moduleResolution: nodenext`.
Spread `createDtsExplicitExtensions({ packageDir: __dirname })` into the
remaining 20 `dts(` call sites, in the form pinned by the two #5365 landed.
Adds `scripts/__tests__/vite-dts-wiring-ratchet.test.ts`, which serves BOTH
halves of this branch: one AST walk over every `vite.config.ts` that calls
`dts(`, asserting both shared factories are spread. Without it each wiring is a
line whose absence is invisible, which is how 1-of-22 and 2-of-22 came to exist.
Part of #5439
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3221.3 KB3990.2 KB
Main entry chunk (gzip)153.7 KB350 KB
Entry fileindex-C7xifsvv.js
StatusPASS

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


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.38KB3.90KB
app-shell (runtime-config.js)18.10KB6.51KB
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)505.15KB114.53KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)168.48KB46.47KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.35KB34.45KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)244.14KB61.94KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)126.07KB30.78KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)201.05KB54.38KB
plugin-kanban (index.js)52.89KB14.59KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.11KB6.64KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)54.84KB18.43KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.49KB2.14KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@yinlianghui-tw
yinlianghui-tw added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 39f4309Aug 25, 2026
26 checks passed
@yinlianghui-tw
yinlianghui-tw deleted the claude/issue-5483-dts-config-wiring branch August 25, 2026 01:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment