# Porting Manim to TypeScript with Coding Agents

> Source: <https://maloyan.xyz/blog/porting-manim-to-typescript-with-agents>
> Published: 2026-10-10 00:00:00+00:00

# Porting Manim to TypeScript with Coding Agents

**What this post is.** A look at how [manim-web](https://github.com/maloyan/manim-web) was built, read from its git history, pull requests, docs, and public stats. Commit counts come from the git author field and co-author trailers on `main`. Stars, downloads, and the Hacker News score were checked through the public APIs on 10 October 2026.

## Short version

- **manim-web** is a TypeScript port of Manim, the Python engine behind 3Blue1Brown's math videos. Scenes run live in the browser on Three.js and WebGL.
- The first port commit landed on 29 January 2026: **147 files, 55,667 added lines** . Its author was`mayor` , the coordinating agent in a[Gas Town](https://github.com/steveyegge/gastown) workspace. The first npm release, v0.1.0, followed 13 days later.
- Up to 19 February, **101 of 169 commits** came from that agent. On the full history it is 101 of 616, because later work came in under other author names.
- The agents were strong at volume: porting examples from Python, parity fixes, and tests. Test coverage went from 52% to 72% in two days.
- Today: **485 stars, 82,553 npm downloads** in the last 12 months, 27 releases, 6,170 tests.

The goal is on the [projects page](https://maloyan.xyz/projects/#manim-web): Manim gives you a video file, and manim-web puts the same scenes live on a web page, where the reader can touch them.

## What a Manim port has to cover

Manim has a small core and a very large surface. The core is a scene, a tree of mobjects, and animations that change mobjects over time. A `VMobject` is a list of cubic Bezier control points: anchor, handle, handle, anchor. Almost everything on screen is a `VMobject`: circles, arrows, axes, glyphs of text, and LaTeX formulas. The surface is the hundreds of classes built on top of that core.

Python Manim renders a scene to a video file. manim-web keeps the same object model but draws every frame in the browser. The table shows how the main parts map.

| Python Manim | manim-web | 
|---|---|
| `class MyScene(Scene)` with`construct()` | An `async` function that takes a`Scene` | 
| `self.play(...)` ,`self.wait()` | `await scene.play(...)` ,`await scene.wait()` , driven by`requestAnimationFrame` | 
| Mobject tree | Mobjects backed by Three.js objects | 
| VMobject strokes | Three.js `Line2` , a mesh stroke with miter joins, or a signed distance field shader for cubic Bezier curves | 
| VMobject fills | Triangulation with `earcut` | 
| Boolean operations on shapes | `polygon-clipping` | 
| LaTeX through a TeX install | MathJax SVG converted to VMobject paths, or KaTeX rendered to a texture | 
| Text glyphs | `opentype.js` outlines | 
| 3D scenes and camera | Three.js lights, meshes, and orbit controls | 
| Video output | Live canvas, plus GIF and video export | 

The repo also ships `tools/py2ts.cjs`, a regex-based converter from Python scenes to TypeScript. Its docs say it knows about 170 mobject classes, more than 45 animations, and more than 80 method mappings. This is the example from the docs:

```
// Python: self.play(Transform(square, circle))
export async function squareToCircle(scene: Scene) {
  const square = new Square({ color: BLUE });
  const circle = new Circle({ fillOpacity: 0.8, fillColor: PINK });
  await scene.play(new Create(square));
  await scene.play(new Transform(square, circle));
  await scene.wait();
}
```

## Timeline

- **23 January 2026.** Initial commit.
- **29 January.** The Gas Town Mayor commits the port: scene and mobject system, 2D and 3D geometry, KaTeX text and LaTeX, axes and graphing, animations, rate functions, GIF and video export, React and Vue components, and the converter.
- **8 to 11 February.** 3D scenes, cameras, and lights.
- **11 February.** v0.1.0 on npm. The package was renamed from manim-js to manim-web the next day.
- **25 February.** Show HN: 140 points, 25 comments.
- **13 July.** v0.3.24, the 27th GitHub release.

## How the agent work was split

The port phase ran in Gas Town, an open-source workspace for running Claude Code agents. Its [README](https://github.com/steveyegge/gastown) describes the Mayor as the primary AI coordinator, a Claude Code instance with context about the workspace. Three facts from Narek's local Gas Town workspace tie it to manim-web:

- The town was created on 22 January 2026, one day before the repo's initial commit.
- The town's root folder is a git checkout of `maloyan/manim-web` . Its history ends at`ae178da` , the last`mayor` commit, on 19 February.
- The town's event log names one actor, the Mayor. The rig list has no manim-web rig, so no Gas Town worker agents (polecats) were set up for it. The Mayor did the port work itself.

So the port phase had one agent at a time, not a fleet. More agents came later, in the PR phase described below.

Commit authors give a partial picture. The agent wrote under its own author name, `mayor`, with Narek's email. GitHub merges both names into one contributor, which is why the contributors page shows 394 commits for `maloyan`: 293 under Narek's name plus 101 under `mayor`.

| Author on `main` | Up to 19 Feb 2026 | All time (616) | 
|---|---|---|
| `mayor` (agent) | 101 | 101 | 
| Narek Maloyan | 55 | 293 (89 of them merge commits) | 
| Dependabot | 13 | 138 | 
| Outside contributors | 0 | 84 | 

The work after 19 February moved to pull requests. 147 of Narek's own PRs, opened between 1 February and 16 July, were merged. 27 of them carry the Claude Code footer. 16 of his PRs say that a second model, Codex, reviewed the plan, the diff, or both. A typical note from PR #385: the plan was posted on the issue, reviewed with Codex, and Codex flagged three issues, all fixed in the branch. This is a pattern of one agent to write and a different model to review.

The record has one blind spot. The 293 commits under Narek's own name carry only 4 AI co-author trailers, and the PR footers mark only 27 PRs. Neither field shows how much of the rest an agent wrote. This post counts only what the author field, the trailers, and the PR bodies show.

## The hard parts

### Strokes and fills on WebGL

WebGL has no built-in thick curved line, so the code has three stroke paths. `Line2` covers the simple case. A mesh stroke with miter joins gives sharp corners on closed paths. A shader draws each cubic segment as a screen-aligned quad and finds the distance to the curve per pixel with Newton steps. Fills go through `earcut`. Boolean operations first had their own implementation and were rewritten on top of `polygon-clipping` on 31 January, the same day.

### LaTeX

LaTeX work runs through the history from February to May. The first version used KaTeX and drew formulas as images. That works for display but not for path animations such as `Create` and `Transform`, which need real paths to draw a stroke or morph one formula into another. On 14 February the agent added `MathTexSVG`: MathJax renders SVG, and a parser turns it into VMobject paths.

The details then took months. MathJax draws subscripts and superscripts with a `scale()` transform, about 70.7% of full size. The first parser read only `translate()`, so indices rendered at full size and overlapped. PR #28 fixed it on 16 February. Rendering is also async, so `Scene.play()` had to wait for pending renders before it starts an animation. On 12 April the vector path became the default for `MathTex` and `Tex`. Later PRs moved to MathJax 4 (#300) and kept one MathJax instance across bundle chunks (#412).

### Text and the Write animation

Manim's `Write` draws text as if a pen traces each letter. A font gives outlines, not pen paths. The port gets the outline from `opentype.js`, rasterizes it to a grid, thins it to a one-pixel skeleton with the Zhang-Suen algorithm, traces the skeleton into chains, and fits cubic Beziers to them.

### Transforms

`Transform` morphs one shape into another, so both shapes need the same number of points in matching order. Early commits added Bezier-aware point alignment and a glyph-based morph for text to text. `TransformMatching` pairs parts with the Hungarian algorithm. A June fix from an outside contributor corrected corner tangents when cubic chains are resampled (#446).

### Matching Python's coordinate rules

Small differences in frames and centers break many scenes at once. v0.3.23 shipped a breaking change: `getCenter` now matches Python Manim, and `moveTo`, `rotate`, and `scale` move their anchor into the parent frame.

## Where the agents did well

- **Parity by example.** Between 30 January and 12 February, 22 agent commits added or fixed example scenes, several of them converted from Python Manim, and fixed the bugs each one showed: rate functions,`Angle` arcs, fill and stroke colors,`VGroup` scale and shift counted twice.
- **Tests at volume.** Commits from 13 to 15 February add tests in batches of 124 to 616. The test suite had 4,970 tests by 16 February and 6,170 by September.

## Where a human was needed

- **Guardrails early.** On 1 February, three days after the port, lint, formatting, unit tests, pre-commit hooks, CI, and`AGENTS.md` came in through commits under Narek's name. 25 agent commits came before them and 76 after.
- **A review gate.** On 16 February a LaTeX fix went straight to`main` under the agent name and was reverted under the same name. It came back as PR #26, merged without review. PR #27 reverted that too, with the note: "The fix itself is correct but needs to go through proper PR review workflow." It landed as PR #28. The agent identity has no commits after 19 February.
- **Rules written for agents.**`AGENTS.md` now bans every`eslint-disable` directive, with a CI check. It requires shared vector helpers instead of inline cross and dot products. It forbids special cases for the Z axis. The`eslint-disable` ban comes from issue #398, and the vector helpers from issue #340.
- **Watching the file tree.** Two early agent commits are titled "Apply Write glyph stroke changes to root src/ (actual source)" and "Consolidate duplicate manim-js/src into src". The agent had edited a copy of the source tree.
- **Outside contributors.** Six people outside the project had PRs merged. Antonin Peronnet alone has 77 commits, including the transform normalization behind the v0.3.23 breaking change (#417) and the corner tangent fix (#446). Thank you, Antonin. Jairo M. Valdivia moved the project to MathJax 4 (#300), and Christoph Deil landed the three most recent fixes on`main` .

## Results

| Metric | Value | 
|---|---|
| GitHub stars and forks | 485 and 31 | 
| npm downloads, 9 Oct 2025 to 8 Oct 2026 | 82,553 | 
| Show HN, 25 Feb 2026 | 140 points, 25 comments | 
| GitHub releases | 27, from v0.1.0 (11 Feb) to v0.3.24 (13 Jul) | 
| Commits on `main` | 616 | 
| Source, without tests | about 80,000 lines of TypeScript in 245 files | 
| Tests | 6,170 passing (PR #541), about 69,000 lines | 
| Example scenes | 56 TypeScript files | 

## Lessons from the record

- **Add the guardrails in the first days.** Lint, tests, CI, and`AGENTS.md` arrived on 1 February. Coverage went from 52% to 72% two weeks later, and the suite grew to 6,170 tests.
- **Agents need the same review gate as people.** After the direct push and the reverts on 16 February, changes reached`main` through PRs. Of 304 first-parent commits on`main` since 17 February, only 2 have no PR number. The agent identity stopped committing on 19 February.
- **Use a second model as a reviewer.** 16 PRs record a Codex review of the plan or the diff, and PR #385 lists three issues that review caught before merge.
- **Turn repeat mistakes into written rules.** The`eslint-disable` ban (#398) and the shared vector helpers (#340) now live in`AGENTS.md` , and CI enforces the first one.

## Sources

- [manim-web on GitHub](https://github.com/maloyan/manim-web) : git history,`README.md` ,`AGENTS.md` ,`CHANGELOG.md` , and source comments
- [manim-web pull requests](https://github.com/maloyan/manim-web/pulls?q=is%3Apr) , including #27, #28, #300, #385, #412, #446, and #541
- [manim-web releases](https://github.com/maloyan/manim-web/releases)
- [manim-web docs and examples](https://maloyan.github.io/manim-web/) , including the py2ts converter page
- [manim-web on npm](https://www.npmjs.com/package/manim-web) and the npm downloads API
- [Gas Town README](https://github.com/steveyegge/gastown) : the Mayor and worker agent roles
- [Show HN: I ported Manim to TypeScript](https://news.ycombinator.com/item?id=47155375) (25 February 2026)
