Found while implementing #5365, which was fenced by triage to @object-ui/components and @object-ui/layout. Not fixed there: two of the affected packages (plugin-list, plugin-view) had sibling cards in flight, and widening past the fence was explicitly out of that dispatch.
(Generics below are written with a space after < — GitHub's body sanitizer strips < followed by a letter as an HTML tag, and it ate them on the first revision of this issue.)
What was measured, 2026-08-20
Full build in a worktree off main 32ef595f4, counting relative specifiers in every emitted dist/**/*.d.ts (AST-located module specifiers, so JSDoc prose is excluded). Only packages that were built are listed; plugin-ai, plugin-gantt, plugin-map, plugin-markdown, plugin-timeline, plugin-tree were outside the closure built here and are untested, not clean.
| package |
build |
extensionless relative specifiers in dist/**/*.d.ts |
@object-ui/plugin-detail |
vite build |
81 |
@object-ui/plugin-designer |
vite build |
62 |
@object-ui/plugin-chatbot |
vite build |
53 |
@object-ui/plugin-grid |
vite build |
36 |
@object-ui/plugin-dashboard |
vite build |
31 |
@object-ui/plugin-form |
vite build |
31 |
@object-ui/plugin-kanban |
vite build |
22 |
@object-ui/plugin-view |
vite build |
19 |
@object-ui/plugin-list |
vite build |
16 |
@object-ui/plugin-report |
vite build |
14 |
@object-ui/plugin-charts |
vite build |
9 |
@object-ui/plugin-calendar |
vite build |
4 |
@object-ui/plugin-editor |
vite build |
1 |
Every tsc-built package measured clean in the same sweep (auth, collaboration, i18n, mobile, permissions, providers, react, sdui-parser, types, core, fields — the two hits each in core and fields are JSDoc prose, not hops). The split is exactly the one #5365 named: bundler builds emit their typings from the other half of the build, where the source specifier survives verbatim.
The part that is NOT just "#5365 again"
#5365's two packages re-export mostly through export * from './ui'. A star re-export from a module the compiler cannot resolve contributes no names, so the consumer gets a loud TS2305: has no exported member.
These packages re-export mostly through named re-exports — export { useObjectChat } from './useObjectChat'. TypeScript still declares the name; it only fails to give it a type. So the consumer gets no error at all and a silent any.
Measured on @object-ui/plugin-chatbot, probe compiled twice against the same built dist:
type IsAny< T > = 0 extends 1 & T ? true : false;
const assertAny: IsAny< typeof useHitlInChat > = true;
moduleResolution: nodenext — compiles clean, i.e. the symbol IS any.
moduleResolution: bundler — TS2322: Type 'true' is not assignable to type 'false', i.e. properly typed.
That is the worse of the two failure modes: #5365 was a wall a consumer hit immediately, this one is a type hole that opens quietly the moment a consumer pins nodenext. It is what produces the 21 residual TS7006 on @object-ui/app-shell reported in #5365's PR.
Shape of the fix
Mechanical, and the mechanism already exists: #5365 landed scripts/vite-dts-explicit-extensions.ts, a shared vite-plugin-dts beforeWriteFile / afterBuild pair that resolves each emitted specifier against the source tree the output mirrors, throws on anything it cannot resolve, and re-checks the emitted set afterwards. Wiring it into each package's vite.config.ts is three lines per package, identical to the two already done.
Worth deciding as one batch rather than 13 cards, and worth deciding together with whether the repository wants the typings-level gate leg that #5365's triage deliberately deferred — without it, nothing stops the 14th package.
Refs #5365, #4538.
Found while implementing #5365, which was fenced by triage to
@object-ui/componentsand@object-ui/layout. Not fixed there: two of the affected packages (plugin-list,plugin-view) had sibling cards in flight, and widening past the fence was explicitly out of that dispatch.(Generics below are written with a space after
<— GitHub's body sanitizer strips<followed by a letter as an HTML tag, and it ate them on the first revision of this issue.)What was measured, 2026-08-20
Full build in a worktree off
main32ef595f4, counting relative specifiers in every emitteddist/**/*.d.ts(AST-located module specifiers, so JSDoc prose is excluded). Only packages that were built are listed;plugin-ai,plugin-gantt,plugin-map,plugin-markdown,plugin-timeline,plugin-treewere outside the closure built here and are untested, not clean.dist/**/*.d.ts@object-ui/plugin-detailvite build@object-ui/plugin-designervite build@object-ui/plugin-chatbotvite build@object-ui/plugin-gridvite build@object-ui/plugin-dashboardvite build@object-ui/plugin-formvite build@object-ui/plugin-kanbanvite build@object-ui/plugin-viewvite build@object-ui/plugin-listvite build@object-ui/plugin-reportvite build@object-ui/plugin-chartsvite build@object-ui/plugin-calendarvite build@object-ui/plugin-editorvite buildEvery
tsc-built package measured clean in the same sweep (auth,collaboration,i18n,mobile,permissions,providers,react,sdui-parser,types,core,fields— the two hits each incoreandfieldsare JSDoc prose, not hops). The split is exactly the one #5365 named: bundler builds emit their typings from the other half of the build, where the source specifier survives verbatim.The part that is NOT just "#5365 again"
#5365's two packages re-export mostly through
export * from './ui'. A star re-export from a module the compiler cannot resolve contributes no names, so the consumer gets a loudTS2305: has no exported member.These packages re-export mostly through named re-exports —
export { useObjectChat } from './useObjectChat'. TypeScript still declares the name; it only fails to give it a type. So the consumer gets no error at all and a silentany.Measured on
@object-ui/plugin-chatbot, probe compiled twice against the same builtdist:moduleResolution: nodenext— compiles clean, i.e. the symbol ISany.moduleResolution: bundler—TS2322: Type 'true' is not assignable to type 'false', i.e. properly typed.That is the worse of the two failure modes: #5365 was a wall a consumer hit immediately, this one is a type hole that opens quietly the moment a consumer pins
nodenext. It is what produces the 21 residualTS7006on@object-ui/app-shellreported in #5365's PR.Shape of the fix
Mechanical, and the mechanism already exists: #5365 landed
scripts/vite-dts-explicit-extensions.ts, a sharedvite-plugin-dtsbeforeWriteFile/afterBuildpair that resolves each emitted specifier against the source tree the output mirrors, throws on anything it cannot resolve, and re-checks the emitted set afterwards. Wiring it into each package'svite.config.tsis three lines per package, identical to the two already done.Worth deciding as one batch rather than 13 cards, and worth deciding together with whether the repository wants the typings-level gate leg that #5365's triage deliberately deferred — without it, nothing stops the 14th package.
Refs #5365, #4538.