Skip to content

Superset Engineering

How AI Loops Moved Our Front End from AngularJS to React

· 7 min read

Every tile is a screen, and they all look the same. Hover or drag to look underneath: teal screens are already React.
Contents
  1. Why a rewrite didn’t work
  2. Same experience, new app underneath
  3. Know the old app before touching it
  4. One loop per screen
  5. Tests are the specification
  6. Humans where they matter
  7. Shipping features during the migration
  8. By the numbers
  9. The lesson

Our main web app is a large enterprise front end, and it was built on an old version of AngularJS. We needed to move it to React. Two rules made that hard: feature development could not stop, and users were not supposed to notice anything had changed.

We did it with AI, but not by asking AI to rewrite the app. We broke the work into small pieces, ran each piece through a loop, and let tests decide when a piece was done.

Why a rewrite didn’t work

The obvious idea is to point an AI agent at the old code and ask for a React version. We tried it. Every time, the AI missed a feature somewhere, or invented one that was never there.

In a large app, a missing feature is easy to overlook in review and hard to find later. So we stopped treating the migration as one big rewrite. Instead we:

  1. broke the app down into individual screens;
  2. wrote tests that described what each old screen actually did;
  3. migrated one screen at a time, and only accepted it when the same tests passed on the new one.

Same experience, new app underneath

The second rule was that the user experience could not change. Every component had to stay where it was, look the way it looked and behave the way it behaved. The React version of a screen is a translation of the old one, not a redesign.

To make that possible while the migration was still in progress, we built a React shell. The shell owns the outer frame: navigation, login and layout. A screen that has been migrated renders in React inside the shell. A screen that has not been migrated yet loads the old AngularJS app in an iframe.

The old app runs in that iframe in an embedded mode. It hides its own navigation and uses the same design tokens as the React side: the same font, padding, buttons and everything else. So the two halves look the same, and a user can’t tell whether the screen in front of them is old or new. When a screen is migrated, the shell swaps the iframe for the React version, and nothing visible changes.

React shell navigation, login, layout Migrated screen rendered in React Not migrated yet old AngularJS app in an iframe, embedded mode: no chrome of its own Same design tokens: font, padding, buttons
Figure 1. The hybrid app, with two screens shown side by side for the figure (in use, the shell shows one at a time). Both read the same design tokens, so a user can't tell which is which. Migrating a screen swaps the iframe for React.

Know the old app before touching it

Before any code was written, the loops studied the old app. We wrote seven AI skills for this reconnaissance. Run over each tab and each piece of functionality, they documented its workflows, the APIs it calls and its edge cases.

That documentation became the brief for everything after it. An agent migrating a screen worked from a written description of what the screen does, not from its own reading of old code.

One loop per screen

The migration itself ran as a loop, one screen at a time, starting with the lowest-hanging fruit:

  1. Pick the next easiest screen that hasn’t been migrated.
  2. Document it, using the recon skills.
  3. Write tests against the old screen: API tests that call the real sandbox APIs, and end-to-end UI tests that click through it in a browser.
  4. Migrate the screen to React.
  5. Run the same tests on the new screen. If anything fails, fix it and run them again, until everything passes.
  6. Check it as a human on a preview environment, then merge. The shell starts serving the React version.
Pick next screen Document it Tests on old screen Migrate to React Same tests on new Human check Merge, swap iframe fail: fix, rerun pass next screen
Figure 2. The loop each screen goes through. The tests written against the old screen are the bar the new one has to clear, and a person looks at it only once it passes.

We leaned heavily on what we call loop engineering1 By loop engineering we mean designing the loop an AI agent runs in: what it picks up next, how its output is checked, when it retries and when a person steps in. to run this across the whole app.

Tests are the specification

The most important step is the third one. Tests written against the old screen are an exact, executable description of what it does. Re-creating the same tests for the new screen turns “is the migration correct?” into a question with a yes or no answer.

Two kinds of test did the work. API tests run against the real sandbox APIs, so they check what each screen actually sends and receives, not what the AI thinks it should. End-to-end UI tests click through the screen in a browser, the way a user would. A feature the AI forgot shows up as a failing test, not as a bug report months later.

Humans where they matter

Automated tests catch a lot, but not everything. So every migrated screen also got a human check before it was merged.

For that we used burstable verify boxes: on-demand preview environments created for a single branch and removed when they’re no longer needed. A reviewer could open the migrated screen against the sandbox and click through it. Because the loops had already proved each screen against its tests, human QA for 183 workflow screens took only seven days.

Shipping features during the migration

Feature development did not stop. For the interim, new features were built in both apps, the old one and the new one, so the product kept moving while the migration caught up.

By the numbers

Workflow screens through human QA183
Days of human QA7
New lines of code, app and tests6 million
AI skills written for migration recon7

The lesson

AI did most of the typing in this migration, but it was not what made it work. What made it work was the structure around the AI: a shell that let old and new run side by side, documentation written before any code, tests that pinned down what the old app actually did, and loops small enough that each one could be checked.

Asking an AI to rewrite a large app in one go gets you something that looks finished and quietly isn’t. Asking it to move one well-understood screen at a time, against tests it can’t argue with, gets you a migration your users never notice.