Back to Blog

Refactoring a 19,846-Line Monolith with Claude Fable 5

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.

MetricBeforeAfter
App.tsx size19,846 lines9,776 lines (−51%)
Extracted modules25 (hooks, logic modules, render sections)
Largest single block moved1,892-line 3D scene initializer
Tests passing855855 (identical)
Intended behavior changesNone

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:

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:

  1. 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.
  2. 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.
  3. 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.
  4. Never deploy without knowing your rollback. Immutable deployments make courage cheap. Check what your rollback story is before you need it.
  5. 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 monolith claude fable 5 llm development react code organization app.tsx ef-map