test(console-starter): record unresolvable relative imports in the alias closure - #5484
Merged
os-support-ai merged 2 commits intoAug 21, 2026
Merged
Conversation
…ias closure `computeClosure()`'s walk had two branches. The bare-specifier branch pushed a miss onto `unresolvable`; the relative branch dropped one with no record, so `expect(closure.unresolvable).toEqual([])` was structurally empty for that whole specifier class — it could not fail no matter how many relative imports the walk failed to follow. That is what let objectui#4538's and objectui#5214's conversions to explicit extensions truncate this walk while landing green: the `filesWalked` floor was the only signal, and it had enough slack to hide two pull requests' worth of drift before app-shell pushed the count under it. Record the relative miss the same way, bounded to specifiers that are meant to be modules — no extension, or a JS/TS emitted extension. Assets (`.css`, images, fonts, `.json`) and Vite resource queries cannot be resolved by design, so they are skipped explicitly into `nonModuleSkipped` rather than reported as defects. Pinned by fixtures over the real walker: `./Foo.js` reaching `Foo.tsx`, planted misses of both module spellings being named, and assets staying out of `unresolvable` while remaining accounted for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
…s-only Empty frontmatter: no package src/ is touched, so this publishes nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
os-support-ai
marked this pull request as ready for review
August 21, 2026 02:54
os-support-ai
deleted the
claude/issue-5386-alias-closure-relative-miss
branch
August 21, 2026 02:54
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 #5386
computeClosure()inexamples/console-starter/test/vite-alias-closure.test.tswalks two kinds of specifier. The bare-specifier branch pushes a miss ontounresolvable. The relative branch dropped one with no record, soexpect(closure.unresolvable).toEqual([])was not a weak assertion for relative imports — it was a structurally empty one, unable to fail no matter how many the walk failed to follow.This is the reporting half. The resolution half landed in objectui#5357 and is already on
main.Re-measured on current
main, not taken from the cardMeasured with the walker's own logic against
origin/mainat 490f482:mainfilesWalkedclosure.unresolvablereports[][]The bottom row is the defect. Ablating the resolver reproduces the objectui#4538 / objectui#5214 regression class on today's tree: 275 specifiers vanish, 28% of the walk is lost, and the assertion that exists to report exactly that still reads
[]. Only thefilesWalkedfloor could ever see it, and the card's history shows it had slack to absorb two green landings.Newly-visible specifiers on this branch: zero, and the assertion stays green
Making the branch record misses surfaces 0 previously-invisible specifiers, and
expect(closure.unresolvable).toEqual([])stays green. No exemption, allowlist or suppression was added to achieve that — the count is genuinely zero because objectui#5357's resolver repair is already onmain, so every one of the 1914.jsand 1018 extensionless relative specifiers in the closure resolves.A zero is only a reading if the method can still report a non-zero, so both directions are counter-probed below rather than asserted.
The asset-specifier boundary, and where the line is drawn
ts.preProcessFilereports every import specifier, including./styles.cssand./logo.svg, whichresolveModulecannot resolve by design — its candidate list is JS and TS only. Recording those identically would manufacture failures that are not defects.The line is drawn on intent, not on whether resolution happened to succeed:
MODULE_EXTENSIONSis derived fromRESOLVABLEso the two cannot drift, plus.mts/.cts). Recorded inunresolvable, which is asserted empty.?queryor#hash(a Vite resource specifier such as?raw,?url,?inline). Recorded in a separatenonModuleSkippedlist, which is never asserted empty.Drawing it on intent rather than on outcome matters here. The closure contains two
.cssimports today, and they resolve only becauseresolveModulereturns any path that exists on disk verbatim — a stylesheet that is virtual, generated, or shipped only indistwould land in the miss branch the moment it appeared. Assets are skipped explicitly into a list rather than by falling off the end of a branch, so the skip stays auditable and this branch cannot go quiet by accident again.Reverse verification
Every leg predicted its direction before running. This file is executed from source by vitest and imports no package
dist, so no rebuild separates the edit from the measurement.Leg A — re-blind the relative branch (restore the bare
continue).Predicted: the two counter-probe fixtures go red; the real-tree assertions stay green, because a healthy tree has zero misses.
Observed, exactly:
2 failed | 7 passed, withexpected [] to have a length of 2andexpected [] to have a length of 5— andreaches the workspace graph it is meant to coverpassed. That asymmetry is the card's whole point: the production assertion cannot detect its own blinding. Only the fixtures can.Leg B — ablate the resolver's
.jsto source fallback.Predicted: 3 red / 6 green, and the real-tree test fails on its floor assertion, which is written first and short-circuits the list.
Observed, exactly:
3 failed | 6 passed,expected 402 to be greater than 500.Leg B-prime — same ablation, floor neutralized so the list assertion's own message is exposed.
Predicted: the list now names roughly 275 specifiers where pre-fix it stayed
[].Observed:
AssertionError: expected [ …(275) ] to deeply equal [], each entry naming the specifier and its importer:The gate now reports the regression as itself instead of as a number that drifted toward a floor.
Restore — both mutations reverted via
git checkout,git statusandgit diff HEADboth empty (byte-identical to the commit), zero ablation markers left in the file, and the suite re-run green at 9/9.Pinning the class by fixture
Four fixtures drive the real
computeClosureover throwaway trees built inmkdtemp, so a future edit cannot silently re-blind the branch:./Foo.jswalked through to theFoo.tsxon disk, subtree intact — the case that produced every row of the table above../missing-module.jsand extensionless./gone) named inunresolvable— the counter-probe for the zero.unresolvablewhile still accounted for innonModuleSkipped..and..as extensionless directory imports.computeClosure()gained an optional entry-directory parameter to make this possible; the production call site is unchanged.Gates, all on final head 14c1b5e
vitest run --project unit examples/console-starter/test/vite-alias-closure.test.tspnpm --filter @object-ui/example-console-starter type-checktscinvocations echoed includingtsconfig.test.jsonpnpm --filter @object-ui/example-console-starter lintcheck:control-byteschangeset:checkcheck:esm-specifiers,check:phantom-deps,lint:coverage,type-check:coverageThe dependency closure was built first (
pnpm --filter '@object-ui/example-console-starter^...' build) — without ittype-checkfails on unbuilt@object-ui/*declarations insrc/, which is unrelated to this diff.Changeset is empty-frontmatter: no package
src/is touched and the example is private, so this publishes nothing.No test is skipped, disabled or quarantined. Scope held to the one declared file plus the changeset.
Generated by Claude Code
Generated by Claude Code