Milestone 1 · February–March 2026
Customer story · September 2026
Durable Migrates a Key Legacy Module to TypeScript with Zerg
A 21-day agent run migrated a key legacy module to TypeScript and React for under $500 in model tokens, while exposing the visual checks needed before production.
- February 9 – March 2
- 21 days
- Reported model-token cost
- Under $500
- Input tokens processed
- ~400M
- Durable engineers added
- 0
- Files passing functional + visual checks
- ~30%
We are not a refactor team; this is the kind of work we want a partner to absorb.
About Durable
Durable builds AI tools that help small business owners create a website and run their business. Its product runs on a mature JavaScript web application, with years of interface behavior and business logic built into the code.
This engagement focused on migrating a key legacy module to TypeScript and React within that running application. Zerg worked against the real app and its existing tests, with a small dedicated Zerg engineering team supporting the autonomous loop.
The challenge
TypeScript offered stronger contracts, better tooling, and more confidence when changing code. Getting there meant more than adding file extensions: component props, callbacks, state, and untyped UI primitives needed to fit together without breaking the product around them.
A manual migration would compete with the work Durable's engineers were doing for customers. Durable wanted a partner to absorb that conversion work while its team kept building features.
Why Zerg
JavaScript and TypeScript can coexist while a migration is underway. That makes it possible to convert a bounded batch, run the app, check the result, and fix failures before moving on.
Zerg's application harness made this an executable loop. Custom spinup configurations and manifests gave the agent a consistent way to start Durable's app, inspect its code, and run its own tests. The source already used React idioms; the agent inferred TSX as the target without an explicit instruction to produce React-flavored TypeScript.
How the migration ran
The autonomous run lasted 21 days, from February 9 to March 2, 2026. The project readout records 36 routes exercised across three phases, with roughly 100 cycles per route on average and approximately 400 million input tokens across the run.
Zerg first established the application harness, then converted files in batches. Babel was part of the conversion workflow; specifications and the app's Playwright tests supplied feedback on each iteration. The checks covered TypeScript conventions, functional behavior, linting, import resolution, and application health.
When a batch failed a check, the agent used the failure information to revise the code and try again. A bounded cycle budget provided an escalation point: batches that did not converge paused for a Zerg engineer to investigate before they were re-queued.
- 01
Harness the application
Start the real app using custom spinup configurations and manifests.
- 02
Convert a file batch
Migrate a bounded set of JavaScript files to TypeScript or TSX.
- 03
Run the verification
Check specifications, Babel output, and the app’s Playwright tests.
- 04
Fix, repeat, or escalate
Use failures as feedback; pause for engineering triage at the cycle budget.
File-batched JS → TSX conversion, verified against the running application.
What Zerg built
The output included migrated TSX components, explicit prop contracts, typed state and callbacks, and adapters around existing JavaScript UI components.
The typed-wrapper pattern was particularly useful. Where Select, Switch, and ButtonGroup did not expose the types the migrated code needed, Zerg introduced TypedSelect, TypedSwitch, and TypedButtonGroup. These declared the expected props at the integration boundary while preserving the original JavaScript implementation.
The excerpts below come from the migration examples shared with Durable. They show selected declarations, with surrounding implementation omitted. Type assertions bridge an existing boundary; they do not add runtime validation.
Before · JavaScript
export default function Gallery({
website, block, previousBlock, onEdit
}) {
const contentRef = useRef(null);
// …
}After · TSX
type GalleryProps = {
website: Record<string, unknown>;
block: Record<string, unknown>;
previousBlock?: Record<string, unknown>;
onEdit?: (element: string, item?: number) => void;
};
const contentRef = useRef<HTMLDivElement>(null);Selected declarations from the migration examples. Props gain an explicit contract, and the ref identifies its DOM element.
Before · JavaScript
<Select
label="Navigation style"
values={navStyles}
// …
/>
<Switch
enabled={block?.menu?.collapse}
// …
/>After · TSX
const TypedSelect = Select as React.FC<{
label?: string;
placeholder?: string;
values: Array<{ id: string; name: string }>;
value?: { id: string; name: string } | null;
onChange: (val: { id: string; name: string }) => void;
disabled?: boolean;
// …
}>;
const TypedSwitch = Switch as React.FC<{
enabled: boolean;
onChange: (enabled: boolean) => void;
label?: string;
// …
}>;Excerpted type adapters declare the expected props while the original Select and Switch implementations stay in place.
Before · JavaScript
const [edit, setEdit] = useState(null);
const [selectedItem, setSelectedItem] = useState(null);
const updateItem = (item) => {
// …
};After · TSX
type PictureItem = {
media?: {
url?: string;
preview?: string;
description?: string;
author?: string;
};
altText?: string;
idx?: number;
};
const [edit, setEdit] = useState<string | null>(null);Selected excerpts from the gallery editor show a named picture shape and an explicit nullable state type.
Milestone 1 results
By the March 2026 readout, Zerg had produced a working demo with the key legacy module converted to TypeScript and React. The migrated work cleared the functional verification gates used by the run.
The reported model-token cost was under $500, using gpt-5-mini for specification analysis, interpreting test results, and code modification. The run processed approximately 400 million input tokens. This figure covers model tokens; it is not the total price of the engagement or a measure of engineering time.
No additional Durable engineers were added for the migration. A small Zerg engineering team set up the harness and handled batches that needed triage.
Functional success was only part of the result: roughly 30% of files passed both functional and visual checks at Milestone 1. The working demo and low token cost did not establish production readiness.
The verification gap
Durable's Playwright checks tested functional behavior. They did not fully capture DOM structure and visual fidelity. Over a long enough run, an agent could find a change that satisfied those functional gates while degrading the rendered interface.
That gap matters in a UI migration. A passing test can confirm that an action completes while missing that the page no longer looks or behaves correctly in ways the test does not observe. The Milestone 1 readout therefore kept functional conversion and visual quality separate.
The next workstream is to make the missing requirements executable: DOM comparisons, visual checks, and structural assertions that the agent must satisfy alongside the existing functional suite. The lesson is practical: a continuous migration loop is only as complete as the behavior its verification can observe.
Zerg's agent harnessed our app, and iterated over a key legacy module, running every batch through Babel plus our own Playwright tests to ensure that every step of the migration was correct. When it hit untyped UI primitives, it even built typed wrappers rather than modifying the originals. It's the most rigorous long-running agent flow I've seen
What comes next
Put your legacy code to work.