Repository navigation
fix(sync): a lazy route relinks a page that appears below its barrel - #2560
Merged
Merged
Conversation
A lazy React Router route whose module is a barrel, and whose page sits in a folder below the barrel's, stayed unlinked after `codegraph sync` added the page or gave it its export, though a fresh index linked it. In DigiShop.Frontend, `@/pages` is `src/pages/index.ts`, which imports `./Home`, `src/pages/Home/index.tsx`, and exports it again. While the page is missing, the route's reference fails and waits under the key of the module it names, `module:pages` (#2422). The retry looks failed references up by the keys of the changed files, and a file in `pages/Home/` gives only `module:Home` and its own stem. The limit, which the `export *` barrels of #2436 share, was recorded with #2558. After the failed-ref retry, which makes the barrel's import edge into an added file, sync now walks up the import edges from each file it added, or gave a definition, to the barrels that pass it on: `export … from`, or an import exported again (`import { X } from './x'; export { X }`, `export default X`), as deep as the resolver chases a name. It re-opens, for the sweep, the failed references waiting under those barrels' keys. Only keys a failed reference waits under are looked up, in one DISTINCT over the failed rows parked under module keys, so a sync with none reads nothing, and a body-only edit seeds nothing. The ImportReader that the added-files reopen (#2512) uses is shared, and gains exportedBindings, behind a regex pre-check, since most importers export nothing. defaultExportBinding moves to alias-binding.ts for this walk and for react.ts's exportsBinding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
A lazily loaded React Router route can name a barrel whose page sits in a folder below it:
A fresh index links this route to
HomePage(#2558).codegraph syncand the file watcher don't, if the page arrives after the route: the file is added later, or an edit gives it the export the route reads. While the page is missing, the route's referenceimport:@/pages#HomePagefails and is parked under the key of the module it names,module:pages(#2422). Sync retries failed references by the keys of the files it changed, andsrc/pages/Home/index.tsxonly offers its own folder's and stem's keys (module:Home,module:index). So the route stayed unlinked untilpages/index.tsitself changed or the project was re-indexed. #2558 recorded this limit inframework-coverage.md, and #2436'sexport *barrels have it too.Fix
reopenReferencesThroughBarrelsinsrc/resolution/module-importers.tsruns right after the existing failed-ref retry and the added-files reopen (#2512). The retry has already made the barrel's import edge into the added file at that point.definitionDelta). A body-only edit seeds nothing.importsedges up to the importers that pass the module on. That meansexport … from/export *, or an import they export again:import { X } from './x'; export { X }orexport default X. These are the formsexportedComponentfollows (fix(react-router): a lazy route links the page a barrel module forwards #2436, fix(react-router): a lazy route follows a barrel that re-exports an import #2558). The walk goes as deep as the resolver chases a name (REEXPORT_MAX_DEPTH).Cost guards:
getFailedModuleKeysis oneDISTINCTover the failed rows parked undermodule:*keys, served by a partial index: at most 0.17 ms on the six real indexes measured, Serviceos's 340k failed refs included. So a sync with nothing waiting reads nothing more.ImportReaderis shared with the added-files reopen. Its newexportedBindingsruns behind a regex pre-check, since most importers export nothing they import.defaultExportBinding(the bindingexport default X;/export { X as default }hands over) moves toalias-binding.ts. The walk andreact.ts'sexportsBindingboth use it, andexport default withAuth(Page)is still followed nowhere.Verification
Regression test
__tests__/sync-nested-barrel-route.test.tscovers three barrel shapes:export … frombarrel,@/pages→pages/home/index.ts→HomePage.tsx;@/shop→shop/cart/index.tsx;export *chain three folders deep,@/admin→dashboard→panel→Dashboard.tsx.It has three cases: the pages appear (also compared with a fresh index), an edit gives the pages their export, and a page is deleted and comes back. All three fail on main (e30a2c1) and pass with the fix.
Mutations. Dropping the import-forwarding branch of
passesOnloses/cart. Dropping the definition seeds fails only the edit case.Suites. The related suites pass 355/355 (22 files). The full suite has 6,681 passing, 0 failing and 404 skipped.
Fresh index, 28 repos (bulletproof-react, milo-ui, outline, proshop, React Router's examples, DigiShop.Frontend, Serviceos and 21 more): dumps are byte-identical between main and the fix, so the
exportsBindingrefactor is behaviour-preserving.Late-file sync, same 28 repos. Each run holds out every file a route links to in a fresh index, runs
init, restores the files, runssync, and compares with a fresh index of the same tree:import:@/pages#HomePage…#OrdersPagestayfailed)The 27 repos include outline, React Router's examples and Serviceos, whose synced graphs differ from a fresh index in the same way on both arms (30/31, 7/7 and 10201/10198 lines). That difference predates this PR.
Time of the new
sync-barrel-modulesphase (CODEGRAPH_SYNTH_TIMINGS=1): 0–6 ms on 22 repos, 46 ms on outline (187 files added at once), and 103 ms on Serviceos (258 added, nothing waiting under a barrel's key). Four more repos had no route target to hold out. The phase reopened 15 references on DigiShop and 1–12 on five other repos. Those failed again in the sweep, so their synced graphs equal main's.Overlap
module:<stem>. This PR reopens them under a barrel's key.lazyModules) re-opens routes that had resolved. This PR handles routes whose reference failed. Trap 17 inframework-coverage.mdnow covers both.ImportReader.src/index.tsabout 140 lines above this change, and fix(react): a component a file declares itself as a value is what its JSX renders #2420/fix(react): a JSX tag renders what the file imports under that name #2451 leaveexportsBindingalone.🤖 Generated with Claude Code