Repository navigation
refactor: no barrel imports or import cycles inside the library - #1619
Conversation
List every export of every package.json subpath, with its kind (value, type, or both), in src/__snapshots__/public-api.md, as tasty does. A package re-exported whole (`export * from '@tenphi/tasty'`) is one line, so a dependency bump does not rewrite the snapshot. A runtime check of the main entry catches a value that silently became type-only. The restructure that follows moves files and rewrites imports; this is what proves it leaves the public exports unchanged. Part of CUB-7438 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Point every relative value import in runtime code and stories at the module that defines the name, resolved with the TypeScript compiler, so a file loads only what it uses. Root now reaches 126 of the library's modules instead of 195, Button 47 instead of 74, TextInput 97 instead of 152. The compound Button moves from the actions category barrel into its own entry, actions/Button/index.ts, next to the Layout and Form compounds: ButtonSplit renders the base Button, so it cannot live in Button.tsx. Stories import compounds from a component's own entry; runtime code imports the base. Break the import cycles the barrels hid: - Form.tsx and ModernFormRoot.tsx: what both roots render moves to Form/FormContext.tsx (contexts, FormScopeMask, useFormProps) and Form/FormElement.tsx (the form element, formPropNames). - Modal: the type-only imports become `import type`. - ColorPicker and ColorSwatchGroup render each other, only at render time; the cycle stays, with a reason. The public exports are unchanged (src/__snapshots__/public-api.md). Part of CUB-7438 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add repo/no-barrel-import to the repo-local oxlint plugin. A relative value import that resolves to an index file is an error in runtime code; stories may also import a component's own entry (`../Button`) for its compound API. Type-only imports, index files, specs, fixtures and the eslint-plugin tooling are exempt. Enable import/no-cycle for src and .storybook, and lint .storybook in `pnpm oxlint` as lint-staged already does. Oxlint runs a module graph only when the import plugin is on at the config root; categories stay off, so it adds no other rule. docs/rules/coding.md#imports, AGENTS.md and the form AGENTS.md file map describe the rules and the new Form files. Part of CUB-7438 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four barrels lost their last importer in the rewrite and are not package entries: _internal/index.ts, _internal/hooks/index.ts, components/helpers/index.ts and portal/storybook/templates/index.ts. Stories and the DialogFormApp helper that render a plain Button import it from Button.tsx; the compound entry is for stories that use Button.Group or Button.Split. The Notifications story goes back to its entry: .design-sync shims that one module path to the built package, so previews share Root's notification context. Part of CUB-7438 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- no-barrel-import also checks import(), an explicit .ts/.tsx path, and `.`, `..` or a trailing slash, which name a directory even when a file of the same name sits beside it. - The story allowance covers *.stories files only, so helpers under src/stories follow the runtime rule. - The snapshot fails on a re-export that resolves to nothing instead of recording it as a value, treats `import type * as` as type-only, and sorts in a fixed locale. - coding.md names the index files that still define code, and the Icons story's exception. The i18n README imports useI18n from its file. Part of CUB-7438 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leaves "leaves a measured row alone while its container closes and reopens" failed in 3 of 5 full browser runs on this branch with two `data-hovered` mutations: the click in the StatefulPanel spec leaves the pointer where the Select row reappears, and Chrome's hover after the layout change lands inside the settle window or not. Hover is the pointer's state, not a change the wrapper made, so the assertion leaves it out. 4 of 4 full runs pass with it. Part of CUB-7438 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The entry allowance covers *.stories.{tsx,jsx} only; the Portal
templates under storybook/ follow the runtime rule.
- no-barrel-import reads import() with a substitution-free template
literal.
- The snapshot counts `export type * as` as type-only, and compares a
package's `export *` names by symbol before resolving, so a broken
re-export inside a dependency's declarations no longer fails it.
- coding.md: runtime code may take the types defined in an index with
`import type`, only the values are out of reach.
Part of CUB-7438
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
📦 NPM canary releaseDeployed canary version 0.0.0-canary-4df6a4e. |
🧪 Storybook is successfully deployed!
|
🏋️ Size limit report
Compared against main at d29a8bf — run 42668868916, 2026-11-09T17:52:42Z. Third-party packages✅ No third-party package entered or left the bundle, and none grew more than 10 KB past its approved size. To see which modules changed, download the size-limit-statoscope-report artifact from this run and open report.html. |
Describe changes
The first of three PRs that restructure UI Kit's
src/. This one moves no files and changes no public export. It makes the library's internal imports load only what they use, and adds the checks that keep them that way and that hold the next two PRs (filenames, then folder moves) to an unchanged public API.src/public-api.test.tswrites every export of everypackage.jsonsubpath (.,./eslint-plugin,./probe) tosrc/__snapshots__/public-api.mdasvalue,typeorvalue+type, and checks that every value of the main entry exists at runtime. A package the root re-exports whole (@tenphi/tasty,@internationalized/date, …) is oneexport *line. The snapshot is identical to whatmaingenerates. A re-export that resolves to nothing fails the test instead of being recorded.repo/no-barrel-import, a repo-local oxlint rule. A relative value import that resolves to anindex.tsis an error in runtime code and in.storybook. A story may also import a component's own entry for its compound API (Button.Group). Type-only imports, index files, specs, fixtures and the eslint-plugin tooling are exempt. The rules are indocs/rules/coding.md#imports.import/no-cycleis on forsrcand.storybook. One cycle stays, with a reason:ColorPickerandColorSwatchGrouprender each other, only inside render.Button(Button.Group,Button.Split) moves fromactions/index.tstoactions/Button/index.ts, next to the Layout and Form compounds. Runtime code imports the baseButton; none of it uses.Groupor.Split.Form.tsx↔ModernFormRoot.tsxcycle is broken by moving code verbatim. The contexts,FormScopeMaskanduseFormPropsgo toForm/FormContext.tsx. The<form>element andformPropNamesgo toForm/FormElement.tsx._internal,_internal/hooks,components/helpersandportal/storybook/templates. None is a package entry..design-sync/config.jsonshims that module path.pnpm oxlintnow lints.storybookas well, as lint-staged already did.Measured: the library modules a file reaches, counting value imports only.
mainRoot.tsxactions/Button/Button.tsxfields/TextInput/TextInput.tsxThere were 5 import cycles; 1 remains.
The size report shows
All0.45% larger. That is compressed size only: the minified bundle grew by 17 bytes (2,036,043 → 2,036,060, from thesize-limit-reportartifacts of this PR's run andmain's). The same code, concatenated in the new import order, compresses slightly worse. The Button-only bundle is 0.07% smaller.ItemActionsWrapper.browser.test.tsx› "leaves a measured row alone while its container closes and reopens" failed in 3 of 5 full browser runs on this branch; it passed both runs onmain. The click in the previous spec leaves the pointer where the row reappears, and whetherdata-hoveredlands inside the settle window comes down to timing. The assertion now leaves hover out. With that change, 4 of 4 full runs pass.There is no changeset. The public surface is unchanged, which the snapshot proves. The package exports three subpaths and sets
sideEffects: false, so no consumer imports a file this PR touches.Local gates on
cab3d15f, all passing:pnpm lint,check:component-tokens,test:types,diagnostics:hooks --check,diagnostics:form --check,diagnostics:compiler,test:form-store,test:form-react,test:form-compiled,build,check:react-package,test:types:consumer,pack:test,check:consumer-declarations,test:package(compiled and uncompiled),test:compiled,test:browser,test:onceandbuild-storybook.Checklist
Part of CUB-7438
Other information
PR 2 (filename conventions, in place) and PR 3 (folder moves, via a committed script that open branches can re-run) follow. The published docs move to
docs/components/<Name>.mdin PR 3, and Cloud's links are updated in the bump PR.🤖 Generated with Claude Code