The main file behind our EVE Frontier map was 19,846 lines long. One React component — App.tsx — held the 3D scene setup, the route calculator, the killboard wiring, the cinematic mode, the AI command handler, and roughly 750 other pieces of state and logic, all in a single closure. This week we finally broke it apart: 19,846 lines down to 9,776, with 25 new focused modules, zero intended behavior changes, and the whole job done by an AI model working largely unsupervised. This post is about how that happened, what it actually bought us, and what we'd tell anyone else building software the way we do.
How a file gets to 19,846 lines
EF-Map has been built almost entirely by LLMs since day one — a workflow we described in our vibe coding post and the project journey retrospective. The very first versions were written with much smaller models, one little feature at a time, by an operator who (by his own cheerful admission) doesn't write code himself. And here's the thing about LLMs without rules: when you ask for a feature, they add it where the wiring already is. The wiring was in App.tsx, so everything landed in App.tsx. Every session made the file a little bigger, and no session was ever asked to make it smaller.
We even wrote about this before. Eight months ago, when the file hit 9,000 lines, we published "Refactoring a 9,000-Line React Component: When NOT to Split" — a genuinely useful piece about extracting custom hooks instead of forcing an artificial breakup. The hooks helped. The file doubled anyway.
The control group
A newer project of ours — a Sui Move + React app — shipped with coding-standards instruction files from day one: file size limits, module layout, scoped TypeScript and Move guidance the LLMs load on every session. Three months in, that codebase is still clean. Same operator, same workflow, wildly different outcome. The variable wasn't the models. It was the rules.
A closing window with Claude Fable 5
Claude Fable 5 is Anthropic's new flagship model — the strongest coding model we've used to date, and notably good at long, complex tasks when given room to work. It's also, at the time of writing, on a clock: Fable 5 is included in Claude subscription plans only until July 7, 2026, after which it moves to usage-based credits (Anthropic says it aims to restore subscription access once capacity allows). We had a two-day window and one item on the backlog we'd been avoiding for months.
Our normal workflow is quite scripted: draft a tight prompt with one model, spell out the steps, review the diff at each stage. This time we did something different — we gave Fable 5 the whole problem and got out of the way. The instruction, roughly: work on a feature branch, don't break anything, run as long as you need, decide the targets and the method yourself, leave rules behind so this never happens again, and give us a preview deploy to test at the end. That's it. No file list, no step plan.
What free rein actually looked like
What came back was more disciplined than our scripted sessions usually are. Instead of editing the monolith by hand, the model wrote small extraction scripts that moved code by exact line ranges — each run asserting the boundaries matched expected content and aborting if anything had drifted. Every moved block went verbatim: no rewrites, no "improvements," no touched dependency arrays.
The cleverest part was using the TypeScript compiler as a wiring inventory. Move a 1,900-line effect into its own file and the compiler produces a precise list of every variable it can no longer see — that list is the module's dependency contract. Each extracted module declares those inputs explicitly and receives them from App.tsx by matching names, so a mis-wire is a compile error rather than a runtime surprise.
The whole refactor landed as a series of separately verified commits, each one gated the same way: the type checker back to its known baseline, a clean production build, the full test suite (855 passing, with the same two pre-existing failures before and after), and headless-browser screenshots compared against the original UI. If anything ever regresses, the history bisects to a single move.
| Metric | Before | After |
|---|---|---|
App.tsx size | 19,846 lines | 9,776 lines (−51%) |
| Extracted modules | — | 25 (hooks, logic modules, render sections) |
| Largest single block moved | — | 1,892-line 3D scene initializer |
| Tests passing | 855 | 855 (identical) |
| Intended behavior changes | — | None |
The honest question: is the app faster now?
Midway through this project the operator asked the question every developer should ask about a refactor: what did this actually buy us? Does the app run better?
The honest answer is no — and it was never going to. When the site is built for deployment, the bundler compiles every source file, whether that's one 20,000-line file or sixty small ones, into the same optimized JavaScript. Players load an identical app either way. Think of a book: the printed copy is the same whether the manuscript was one giant scroll or organized into chapters. We reorganized the manuscript, not the book.
So who benefits? The editors. And on this project, the editors are LLMs:
- Context economics. A 20,000-line file is far more text than a model can hold at once, so every AI session worked through a keyhole — search for a line number, read a fragment, hope the surrounding context didn't matter. Now an agent fixing the route calculator opens a 500-line module and sees the entire subsystem. Sessions get cheaper, faster, and less error-prone. For a project built 100% with LLMs, tokens are the fuel bill — this refactor is fuel efficiency.
- Explicit boundaries. Before, the scene code could silently reach any of ~750 variables. Now each module declares exactly what it consumes, checked by the compiler. That's documentation that never rots.
- Smaller blast radius. A botched edit in one module can't corrupt an unrelated feature, and every commit names the subsystem it touched instead of "App.tsx changed (again)."
- Better debugging. Stack traces and searches now point at
useStarColorPipeline.ts, not "somewhere in the monolith."
The one-line takeaway
A refactor like this pays off in tokens and error rates, not frames per second. If your codebase is maintained by LLMs, that's not a consolation prize — it's the whole prize.
Rules so it never happens again
The line count was only half the job. The other half was making sure the monolith can't grow back. The repo now has a canonical code-organization standard that every future agent session loads: new files target 400 lines with a hard ceiling of 800, one component per file, and — the rule that matters most — App.tsx is wiring-only. New features get their own modules; the main file only connects them. The standard also documents the extraction playbook and a roadmap for the remaining oversized blocks, so future sessions can keep shrinking the file opportunistically while doing normal feature work.
That's the real lesson of the control-group comparison above: these files cost almost nothing to write on day one, and the price of not having them was this entire refactor.
Shipping it: know your rollback before you deploy
Here's the uncomfortable part of refactoring a live app: after nine months of features, no one can manually re-test everything. Routing, the killboard, cinematic mode, smart assemblies, the log parser, the mobile shell — the surface area of an EVE Frontier tool this size is simply too big for a human smoke test to cover.
What made deploying rational anyway was knowing the recovery story cold before touching production. EF-Map deploys are decoupled from the code repository — merging changes nothing for users until an explicit deploy runs — and every production deployment is kept, immutable, at its own permanent URL. The last known-good build from before the refactor is frozen forever. If something breaks, recovery is a 30-second rollback in the Cloudflare dashboard, or a one-sentence prompt to any LLM: "production is broken, rebuild and deploy the pre-refactor commit." And because this refactor changed no data formats, no stored settings, and no share-link formats, rolling back has zero side effects — the old and new builds are fully interchangeable.
As this post goes live, the refactored app is in production. Now comes the part every developer knows: we sit and wait. If no bug reports come in, the refactor held. If they do, we know exactly how to get back to safe ground in under a minute. That trade — verified changes plus instant rollback, instead of impossible exhaustive testing — is the only honest way to ship something this size.
Takeaways for LLM-first developers
If you're building with LLMs the way we do — describing what you want and letting the models write every line — this project condenses to five lessons:
- Write the rules file on day one. File size limits, where code goes, what the main file is for. LLMs follow written rules remarkably well; without them, every session optimizes for "make this one change work" and the debt compounds silently.
- Give frontier models room on big mechanical jobs. Our usual carefully-scripted prompts would have taken weeks of sessions for this. One open-ended instruction with hard constraints ("don't break anything") and free rein on method finished it in a day — and the model's self-chosen method was more rigorous than our scripts.
- Trust compilers and tests, not vibes. Verbatim moves, compiler-verified wiring, an unchanged test suite, and pixel-compared screenshots are what "nothing broke" actually looks like.
- Never deploy without knowing your rollback. Immutable deployments make courage cheap. Check what your rollback story is before you need it.
- Know what a refactor buys you. Not speed — leverage. Every future change gets cheaper, safer, and easier to review.
What's next
The remaining roadmap lives in the standards doc: a few more oversized blocks come out whenever future work touches their areas, ratcheting App.tsx down toward a thin composition layer. Meanwhile, EF-Map keeps doing what it's for — helping EVE Frontier players route, scout, and fight across 24,000 star systems. If you play, open the map and put the refactored build through its paces. If you find something broken, you now know exactly how fast we can fix it.
Related Posts
- Refactoring a 9,000-Line React Component: When NOT to Split — the prequel to this post, written when the monolith was half this size
- Vibe Coding: Large-Scale LLM Development — the methodology behind building EF-Map without writing code by hand
- Don't Sleep on Claude Cowork: How We Used It with Claude Code for EF-Map — another workflow where Claude models work autonomously on EF-Map
- Audit, Chunk, Implement: The AI Workflow Behind EF-Map — how we break large jobs into verifiable stages