feat(scripts,components): gate unreferenced source files, and delete the one standing orphan - #7670
Merged
Merged
Conversation
…the one standing orphan (objectui#7515) None of the repo's 42 `scripts/check-*.mjs` gates could see a source file that nothing reaches. `check-dist-completeness` asks whether `dist/` holds what `tsc` emits, `check-readme-exports` compares documented exports against shipped ones, `check-i18n-dead-keys` covers message keys — a `.tsx` that is in the published tarball while being reachable from nothing is outside all three. Both instances found this week were found by a human reading unrelated code. `scripts/check-unreferenced-sources.mjs` walks the import graph from a covered package's declared entry AND from every build-config alias whose replacement is a file inside the package. That second leg is the whole difficulty: nothing in `packages/components` imports either `use-sync-external-store` shim, and both are alive only through `vite.config.ts` `resolve.alias` entries whose importer is a bundled dependency. A walk that skips it reports exactly those two live files as dead on its first run. Scope is DECLARED per package in `COVERED_PACKAGES` and the uncovered remainder is printed as a count derived from the workspace on every run. An alias expression the reader cannot evaluate is a FINDING, never a skip: skipping one would make the gate accuse whatever file that alias points at. `packages/components/src/ui/toast.tsx` is removed. It had no importer anywhere under `packages/components/src` and `ui/index.ts` never carried it, so the barrel's `export * from './ui'` did not reach it either; it contributed nothing to `dist/index.js` and nothing to the export surface, while shipping as `dist/ui/toast.d.ts`. `@radix-ui/react-toast` goes with it — the removed file was its only importer in the repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
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
Size Limits
|
os-sam
marked this pull request as ready for review
September 4, 2026 17:10
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7515
None of the repo's 42
scripts/check-*.mjsgates could see a source file thatnothing reaches. The nearest neighbours each answer a different question —
check-dist-completenessasks whetherdist/holds whattscemits,check-readme-exportscompares documented exports against shipped ones,check-i18n-dead-keyscovers message keys — so a.tsxthat is in the publishedtarball while being reachable from nothing is outside all three. Both instances
found in one week (objectui#7319, objectui#7397) were found by a human reading
unrelated code. The hazard is not the bytes: the file objectui#7319 removed carried
the same export name as a live engine one package over and evaluated no predicate,
so name-completion alone could have wired a silently wrong renderer into a
published package.
The card's measurement, reproduced first — and reconciled, not adjusted
The card measured 210 non-test sources reaching 207 on
746f6f818. This branchmeasures 209 reaching 206 on
f7cf7e8a, and the delta is fully explainedrather than tuned away:
7bf244be("remove the duplicate chart primitives",objectui#7397) deleted
packages/components/src/ui/chart.tsxon 2026-09-04, afterthe card was written. 210 − 1 = 209, 207 − 1 = 206, and the same three unreached
files remain. Two independent walkers agree on the population (a throwaway script,
and the gate reusing
listSourceFiles+TOOLING_FILEfromcheck-phantom-dependencies.mjs).One smaller correction to the card's framing, not affecting its conclusion:
"42
check:*scripts" is 42scripts/check-*.mjsfiles, of which 35 are wiredas root
check:*npm scripts.The alias leg is the whole difficulty
scripts/check-unreferenced-sources.mjswalks from two kinds of root:build.lib.entry), andNothing in
packages/componentsimports eitheruse-sync-external-storeshim;both are alive only through
vite.config.tsresolve.aliasentries whose importeris a bundled dependency no source file names. An alias whose replacement is a
directory (
{ find: '@', replacement: resolve(__dirname, './src') }) is not aroot but a resolution rule — and the filesystem decides which is which, not the
spelling.
Design choices worth review:
Skipping one would make the gate accuse whatever file that alias points at, so
the safe direction is to fail loudly on the config.
COVERED_PACKAGES(packages/componentsonly), and the uncovered remainder is printed as a count derived from the
workspace on every run — currently 40 packages with a
src/tree — so itcannot rot into a stale claim. Per the dispatch ruling: a gate that covers one
package correctly beats one that covers forty with false positives.
tsconfigpathsis audited, not assumed.packages/componentsdeclaresits
@alias twice (vite for the bundler, tsconfig for the type program); thegate reads only the first, so it re-derives on every run that the second says
nothing the first does not.
moduleSpecifiers()rather than growing a second parser.Non-vacuity, proved in both directions on the real tree
Predictions were written before each leg; one prediction missed and is reported as
a miss.
ui/toast.tsx)auditAliasMechanismsvite.config.tsexport * from './toast'toui/index.tsThe miss was real and is fixed: the alias
findwas stored as raw source text andthen read with
JSON.parse, which cannot parse a single-quoted TypeScript string,so the gate reported
packages/components' own@alias as unmodelled.findisnow a structured value (
readFind), andscripts/__tests__/check-unreferenced-sources.test.tspins that exact case.
Both mutation legs ran under an
EXIT INT TERMtrap with absolute paths fromgit rev-parse --show-toplevel, were proved on disk by anchored grep counts andgit hash-objectmovement off the HEAD blob, and were restored viagit checkout HEAD -- ABSOLUTE_PATH, verified by emptygit diff HEADandblob-hash equality.
The deletion, proved per file
packages/components/src/ui/toast.tsxis the only genuine orphan; the sweep foundno others inside
packages/components. Unreferenced and not on the publicsurface were checked as two separate questions, each with a lit control:
sonner: 16 referencing filesToastPrimitives/ToastViewportabsent fromdist/index.jsToaster: 3dist/ui/index.d.tsnever names./toastsonner: 2A first attempt at the type-surface question grepped
dist/index.d.tsforToast*and read 0 — but its control read 0 too. That was a dark instrument; the reading
was discarded and rebuilt against the barrel that actually carries
ui/*.It did ship: a before/after full build shows the published
dist/losing exactlyone file,
dist/ui/toast.d.ts(212 → 211), and nothing else — which is why thiscarries a
patchchangeset rather than none.@radix-ui/react-toastis dropped in the same change: the removed file was its onlyimporter anywhere in the repository (control:
@radix-ui/react-dialog, 6 files).This is not an unrelated tidy-up — it is debris this change itself creates.
On the No-Touch zone, and my own clause-② determination
ui/toast.tsxsits inpackages/components/src/ui/**, AGENTS.md commandment #7.That rule forbids modifying the logic or styles of Shadcn-synced primitives,
and its stated reason is that the sync script overwrites such edits. Deletion is
not that act:
getLocalComponents()inscripts/shadcn-sync.jsderives its trackedset from
readdir(COMPONENTS_DIR), so a deleted file simply leaves the tracked setand nothing restores it. Precedent one day old:
7bf244bedeletedpackages/components/src/ui/chart.tsxfrom the same zone and is onmain.Clause-② determination: no, agreeing with the dispatch ruling and reached
independently.
scripts/is not a governed surface here; the gate moves no schemakey and no export. The deletion removes nothing reachable — proved above per file
rather than assumed, and the published export surface is unchanged.
Verification
All commands captured with the exit code redirected before any pipe. Gate union
re-run after the final commit, at
7fdeff55:check:control-bytes,check:phantom-deps,check:self-import,check:published-tsconfig-exclude,check:side-effects-array,check:esm-specifiers,check:dist-completeness,check:readme-exports,check:doc-fences,check:doc-types,check:doc-snippets,check:pre-install-import-graph,check-doc-links,check-lint-coverage,check-unreferenced-sources(OK Every shipped source file in every covered package is reachable.),and the four changeset gates.
packages/components: 232 test files, 2159 tests, all passing;type-checkexit 0 (the script name echoed back, so this was not a zero-match silent pass —
this package spells it
type-check, hyphenated).pnpm type-check:scriptsexit 0. It caught real JSDoc type errors in the gatefirst; the typedefs now match the structured alias shape.
eslint . --no-inline-config, 4260 files): both newfiles 0 errors, 0 warnings, and both were in the linted population.
pnpm --filter @object-ui/components lintexit 0.ci-cd-pipeline-docgate caught the missing doc row for the new step and isnow green;
content/docs/guide/ci-cd-pipeline.mddocuments it in run order.Two failures were investigated and are pre-existing, already-filed, and unrelated
to this diff (my changed paths appear nowhere in either gate or test; control lit):
check-sdui-registration-pins.test.tsfails on any tree wherepackages/app-shell/distexists. Reproduced and isolated: removing thatdistmakes it pass, restoring it makes it fail.
check-readme-exports.test.tsreds on a half-built tree.With those two set aside,
scripts/__tests__/runs 101 files / 3003 tests.PRECONDITION NOT METwas recorded twice and treated as not measured rather thanas a pass or a red:
check:readme-exportsandcheck:doc-snippetsboth need a fullbuild (the first says so itself — "the packages were never built"). Both were
satisfied with
turbo run build --filter='./packages/*'and re-run green.Heavy runs went through the shared verify lock, slot
issue-7515.What is deliberately NOT claimed
makes that file reachable here, because the build config names it. Deciding whether
a bundled dependency still imports
use-sync-external-store/shimmeans walkingnode_modules— a different gate on a different input.by tests is unreachable from the published entry and is reported; the finding says
referenced only by test filesso the two are distinguishable.packages/componentshas no such file today.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code