Repository navigation
fix(sync): a route links the module it lazily loads when the module appears later - #2422
Merged
Merged
Conversation
…ppears later A router names the page or layout it loads lazily by the module's path: React Router's `lazy-import:./pages/Team`, Vue Router's and Angular's `import:./home/home.component#HomeComponent`, each also behind `layout:`. Sync retries a parked failed ref by name_tail (#1240), and these were parked under a fragment of the path ('/pages/Team', 'component#HomeComponent') that no file's keys match. So a route whose module was added after the router was indexed, or whose module gained its component in a later edit, stayed unlinked until the router file changed or the project was indexed again. #2392 fixed the same gap for imports, #2403 for Liquid's path references. - referenceNameTail parks a module reference under the stem of its path behind 'module:' ('module:Team'), a key no symbol's name can be, so name lookups never retry it by a namesake. - Sync adds the changed files' moduleReferenceKeys (importPathKeys behind 'module:') to the symbol retry: an added file can be the module, and an edit can give the module the component the route renders. - Schema v14 rewrites the tails parked before. Its select keeps SQLite off idx_unresolved_status with `+status`, reading idx_unresolved_name ranges instead of every failed row (vscode's index: about 1 s down to ~12 ms). - The retry resolves in row order, as a full index does, so when a route's module reference and its layout reference make the same edge, sync keeps the one a fresh index keeps (ghostfolio's markets page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
colbymchenry
added a commit
that referenced
this pull request
Oct 7, 2026
…n the module appears later (#2450) A default, namespace or aliased import binds a name its module never declares: `import tagsController from './tag/tag.controller'`, `import { Component as GroupsPageWrapper } from './groups_page'`. Every reference through that name (the import, a call, `new`, a base class, a JSX or template tag, an Express mount) was parked under the name, which no file a sync adds or changes carries. So a module added after its importers were indexed, restored after a delete, or given its export in a later edit never relinked them, while a fresh index did. Such a reference is now parked under its project module's key (`module:tag`), which sync already looks up for every file it adds or changes (#2422), and it is still found by its own name: a binding its module never resolves is linked by that name alone, as vben's `{ VbenFormSchema as FormSchema }` from a workspace package is. Schema v15 adds the partial index that by-name lookup needs; its leading `status` keeps the planner off idx_unresolved_status, which read 830K rows on vscode's index. 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.
codegraph syncand the file watcher never linked a route to the page or layout it lazily loads when that module was added after the router was indexed. The same happened when the module was already there and only a later edit gave it the component the route renders. The route stayed unlinked until the router file itself changed or the project was indexed again. This is the same family as #2392 (imports) and #2403 (Liquid path references) and builds on #2403, which has landed.What was broken
lazy: () => import('./pages/Team')lazy-import:./pages/Team(references)/pages/Teamcomponent: () => import('@/views/Login')import:@/views/Login#Login(calls)@/views/Login#LoginloadComponent: () => import('./home/home.component')import:./home/home.component#default(references)component#defaultEach router also has a
layout:form for a layout route's module. #2403 already relinks the React form when the import names the extension (./pages/About.tsx). Vue's../views/Home.vue#Homeand every Angular reference never relinked, with or without one.Cause
Sync retries a parked failed ref by its
name_tail(#1240), looking the tail up among the changed files' symbol names and, since #2403, their file names. These refs name a module by its path, so the dotted tail is a path fragment or whatever follows the module's own dot. No file's keys match it. They are notimportseither, so #2392's added-file path lookup, which is scoped toreference_kind = 'imports', never sees them.Fix
referenceNameTailparks a route's module reference under the stem of the module's path, prefixedmodule:.lazy-import:./pages/Teambecomesmodule:Teamandimport:./home/home.component#HomeComponentbecomesmodule:home. It covers React'slazy-import:<path>and Vue's and Angular'simport:<path>#<export>, each also behindlayout:, forreferencesandcallsrefs (Vue's route ref is a call).indexandtypesare the tails of thousands of failed calls, which is the population behind fix(sync): an import links the file it names when that file appears later #2392's planner and ceiling notes.moduleReferenceKeys(file)isimportPathKeysbehindmodule:: the file's name, its stem and its folder, sopages/Team/index.tsxsatisfies./pages/Team.idx_unresolved_failed_tail, and only module refs carry these tails, so there is no new index.export default, the Vue component or the Angular class is what the route waited for.+status/+reference_kind. With plain terms SQLite picksidx_unresolved_status, althoughfailedis nearly every row: on vscode's index that read 830K rows, about 1 s, to select none. With the+the query is three prefix ranges ofidx_unresolved_name. A test pins the plan.layout:ref make one edge.name_tailindex order, so sync kept thelayout:copy where a fresh index keeps the module ref's.Validation
Tests.
__tests__/sync-route-module-retry.test.tshas 14 tests, and 13 fail without the fix (the one guard test passes either way). Every sync result is checked against a fresh index in a separate folder:indexAll()over an existing index skips unchanged files, so it can't stand in for one. The cases:.tsx, a default export named unlike its file, a folder'sindex.tsx, a tsconfig@/alias rendering theComponentexport, and a lazy layout route..vueand extension-less imports, plus a lazy layout..then((m) => m.X), a default export, and a lazy layout.Two of #2403's expectations in
sync-path-reference-retry.test.tsmove here.lazy-import:./routes/about.tsxandimport:./home.component#HomeComponentare module references now, parked asmodule:aboutandmodule:home.tscis clean. On the rebased branch the affected suites pass, 1,076 tests: sync and retries, the React, Vue and Angular routers,extraction, andkernel-tsjs-paritywithCODEGRAPH_KERNEL_EXPECT=1. The full suite (before the rebase) had 6,407 passed and 7 failed, all on this shared, fully loaded Windows box:"Before" and "base" below are main at dd70646 with #2403 merged in. That is what main holds now, minus #2401, #2402 and #2407, which touch Go and C++ only. I re-ran three of the late cases and their fresh indexes on the rebased branch, and they are unchanged.
Full-index A/B (base vs fix) over 19 repos:
name_tailof 2 and 91 module refs that fail on a full index in both arms. milo-ui's lazy modules are barrels; see below.Late module scenario. Every file a route lazily loads is held out, the rest indexed, the files restored,
codegraph syncrun, and the dump diffed against a fresh index. Differing dump lines:The residues in vue3-element-admin and milo-ui are aliased imports, and they show the same way in the before arm:
import Error404 from '@/views/error/404.vue'andimport { Component as GroupsPageWrapper } from …. I filed them as a follow-up. Ghostfolio reaches 0 only with the row-order retry; without it, 2 lines remain.A generic late scenario, holding out every 7th source file, produces diffs that are byte-identical across base, fix without the sort, and fix. It ran on 8 repos including Go (gin) and Python (django-realworld), so the change leaves other retries alone.
Live watcher (
cg.watch()): React, Vue and Angular routes link after the watcher's sync with the fix, and none link before.codegraph affected src/pages/Team.tsxafter the sync listssrc/router.test.tsxwith the fix and nothing before.Cost on vscode's 1.9 GB index (830K failed refs): the v14 migration takes 11–16 ms. The module-key lookup for 1,000 changed files (2,989 keys) takes about 3 ms.
Schema version
This takes v14. Open PRs #2347 and #2060 still claim v12, which #2392 took (#2403 took v13). Whichever of them lands after this one needs to renumber past v14.
Not in this PR (filed as follow-up tasks)
const Docs = lazy(() => import('./pages/Docs'))(from fix(react-router): read route tables another file hands the router #2400). With the module missing, the route binds to the const. When the module appears, sync keeps that binding unless the module's component shares the const's name, and thejsx-renderedge isn't refreshed. This is a different mechanism: the ref resolved, so nothing is parked to retry.X as Y) aren't retried when their module appears. That is the residue above; it also shows in ts-express, takenote and proshop on the generic scenario, identically before and after.export * from './build_page') never link, even on a full index. These are milo-ui's 91.layout:refs, so ghostfolio's edge carriesframework: react. The target is the same; only the label is wrong.🤖 Generated with Claude Code