What breaks if I delete this file? React tooling can't answer that. - CrossUI Studio — Symmetric Visual IDE
Author
User<br>linb<br>Posts by this author<br>Posts by this author
~10 min read · Engineering
You want to delete a file.
A quick search shows three direct imports. That's easy enough to see.
The harder question is what those three files are used by.
One may be a layout dependency. Another may be part of navigation. A third may feed a page that doesn't look related at first glance.
So the real question isn't "Who imports this file?"
It's "What breaks if I remove it?"
Most tools don't actually keep a dependency graph around as something you can ask questions of. They keep a list of files and a pile of text search. That's not the same thing.
A component tree is not a dependency graph
People mix these up a lot, so worth separating.
Component tree: who renders who, at runtime. Parent to child. React DevTools shows you this one.
Dependency graph: who imports who, at compile time. Directed, sometimes has cycles, crosses files and packages freely.
They're not the same shape. A provider might be a distant ancestor in the component tree, but in the dependency graph it's just one edge coming off main.tsx. You debug with the component tree. But when you're actually changing code, the thing you're navigating is the dependency graph — and that one's never had good tooling.
What a real graph actually looks like
Here's why building this is annoying, not fun-annoying, actually annoying:
Barrel chains. @/components → components/index.ts → chart/index.ts → chart-widget.tsx. Three re-exports before you hit real code. Do you keep those middle hops as nodes in the graph? Keep them and it's noisy. Collapse them and you lose the truth of what's happening. We keep the edges, mark them transitive, and let you collapse the view when you don't care.
Path aliases. @/, ~/, whatever a monorepo's workspace protocol wants to call itself this week. The resolver has to actually understand the build config to draw the right edge, and the build config itself is sometimes three files stacked on each other.
export * ambiguity. Same symbol name coming in from two barrels, and which one wins depends on order. The graph has to remember that order or it's just wrong.
Dynamic imports. React.lazy(() => import('./Page')). Static analysis can see it, but it's an edge that only activates at runtime. Should probably be a dashed line, not a solid one.
Type-only edges. import type doesn't exist at runtime. Delete that file and your app still runs, but tsc breaks. Two different kinds of edges, same graph.
Cycles. Real repos have them constantly, plenty of them harmless. The graph has to represent that, not fall over.
None of this is exotic. It's just Tuesday in a real repo.
Making the graph answer questions
The point isn't "look, we drew a graph." The point is what you can ask it:
Resolve-through — give me the file this import actually lands on, skip every barrel in between, one hop.
Inbound edges — who references me? If I change this file, what screens does it touch? This is the actual answer to that opening question.
Reachability from entry — can you get here from main.tsx at all? If not, it's dead code, or only a test imports it.
Shortest path between two nodes — how does this page even use that util? The path itself is the explanation.
Cut vertices — which files are chokepoints, where one change ripples everywhere? That's an objective basis for refactor priority, not someone's gut feeling.
None of this is a visualization trick. It's turning engineering instinct into something you can actually ask and get an answer to.
The graph is never complete, and that's fine
Real repos always have holes — a missing peer dep, an optional import, an alias nobody set up, a file that flat out doesn't exist anymore.
Two ways to handle that. Throw out the whole graph the second one edge fails to resolve, which is what most tools do. Or mark that edge unresolved and keep building the rest of the graph around it. We do the second. An incomplete graph you can still navigate beats a complete-or-nothing graph every time. (We wrote about the mechanics of that specific move — bypassing a broken import instead of stopping — in an earlier post, if you want the deep dive on that one piece.)
Graph plus symmetric editing
This is where the two pieces click together, and it's the part that's genuinely hard to copy.
Symmetric editing gives you precision inside one node — this thing on screen maps to exactly this AST node. The dependency graph gives you reach across nodes — this AST node's position in the whole repo's topology.
Put together: click a card on screen, and you don't just land on its JSX. You get its full path back to the entry point — through the route, the view, past three barrels, to the real file — plus who else depends on it. A precise map with no way to zoom out is just a precise island. A zoomed-out map with no precision is just a...