Zerg

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.
— Osama Khan, Durable
01

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.

02

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.

03

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.

04

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.

Inside the migration loop
  1. 01

    Harness the application

    Start the real app using custom spinup configurations and manifests.

  2. 02

    Convert a file batch

    Migrate a bounded set of JavaScript files to TypeScript or TSX.

  3. 03

    Run the verification

    Check specifications, Babel output, and the app’s Playwright tests.

  4. 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.

05

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.

gallery/index.tsx · component props and a DOM ref

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.

header/MenuSettings.tsx · typed UI boundaries

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.

gallery/manage.tsx · state and domain data

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.

06

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.

07

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
— Osama Khan, Durable
Next

What comes next

DOM and visual verification

Add structural and visual checks alongside functional tests, then revisit the affected files.

More context-aware batching

Explore grouping related responsibilities to make each batch easier to reason about.

Parallel execution

Explore running independent batches concurrently through ZergCloud, with explicit integration checks.

Put your legacy code to work.

Plan your next migration
with Zerg.

Discuss your migration