Project management¶
What is project management?¶
The repository is the package, manimgx, and holds more than its code:
.
├── src/manimgx/ ← the package: the scene API and the command line
├── rust/ ← the engine's crates: the engine, compiled into the package as
│ manimgx._engine, and the crates it is built with from others'
│ sources, which they fetch
├── tests/ ← the package's tests and the integration corpus
├── fonts/ ← the fonts text is set in, packages of their own:
│ ├── manimgx-fonts/ the Noto fonts
│ └── manimgx-fonts-cjk/ Noto Sans CJK (not in the browser)
├── browser/ ← manimgx for the browser, published on npm, with its tests
├── docker/ ← the Docker image
├── docs/ ← the docs site: its pages, its settings, what its build loads
├── examples/ ← thirty example films, a Python file each
├── scripts/ ← what writes and builds: the release's scripts, the docs' generated
│ pages and pictures, the engine's DFG table, and the benchmark
│ against Manim CE, ManimGL and Blender
├── skills/ ← the agent skill
├── pyproject.toml ← the package, its workspace (with fonts/), the tools and their settings
├── uv.lock ← the exact version of every Python package
├── justfile ← the development tasks
├── .pre-commit-config.yaml ← the checks
├── README.md ← the front page: GitHub's, PyPI's, and the docs' Welcome
├── AGENTS.md ← this guide's commands and rules in brief, for coding agents
├── CITATION.cff ← how to cite manimgx
├── LICENSE ← manimgx's license, the MIT License
├── LICENSE-THIRD-PARTY ← what the wheels hold that others hold the copyright in
├── LICENSE-LAVAPIPE ← the notices of the lavapipe the Linux wheels bundle
├── LICENSES/ ← the text of each license a file of the repository is under
├── REUSE.toml ← whose each file is, and under which license, where it doesn't say
└── .github/ ← the workflows, deployment configs, Dependabot, the
contributing guide, issue forms and pull request template
Project management is everything but the code, in src/, rust/ and browser/src/: what
makes the code installable, the same on every machine, and checked the same way by everyone.
The repository's root is the package; what is published apart from it (the font packages, the
browser package, the Docker image) has a folder of its own.
Why isn't code enough?¶
Code alone leaves two problems open: how users get it, and how developers work on it.
Distribution¶
A user wants pip install manimgx and a working manimgx command. manimgx is harder to
ship than pure Python: its engine is compiled, so every platform needs a build of its own,
with the engine inside. A release publishes manimgx in five forms:
- Wheels, one per platform: Linux (x86_64 and arm64), macOS (arm64, and x86_64
cross-compiled on arm64) and Windows (x86_64). The engine is built against Python's
stable ABI, so a platform's one wheel serves every supported Python, and x264 is built
into it, so a wheel needs nothing from the system. A Linux wheel bundles Mesa's lavapipe
too, to draw on the CPU where there is no GPU driver
(
scripts/release/build_lavapipe.shbuilds it in the manylinux image first: about 24 MB of the wheel). cibuildwheel builds each wheel and installs it on every supported Python, where it must render a scene (scripts/release/smoke_test.py) before it is kept: on Linux, with no driver in the image, so with the bundled lavapipe. Its settings are[tool.cibuildwheel]inpyproject.toml. - A source distribution, which builds the engine on the machine that installs it: that needs Rust and a C compiler, nothing else (the engine's build fetches the sources it takes from others: see Others' sources).
- Executables, one per platform: one file that holds a Python with manimgx installed,
for a machine without Python (see
scripts/). - A Docker image,
ghcr.io/academa-labs/manimgx(seedocker/). - The package for the browser,
manimgxon npm: the player in one file, which runs scenes with the wheel for the browser (seebrowser/).
The fonts text is set in are packages of their own, manimgx-fonts and manimgx-fonts-cjk:
a wheel for every platform, released when the fonts change, not with every manimgx (see
fonts/).
PyPI gets the wheels, the source distribution and the font packages; the GitHub release gets
the wheels, the source distribution, the wheels' complete source (manimgx-X.Y.Z-source.tar.xz:
see Licenses) and the executables; npm gets the package for the browser; ghcr.io
gets the image. Releases says how a release is made.
The development environment¶
Developer A installs the dependencies today and the tests pass. Developer B installs them a month later, gets a newer Typst or numpy, and the tests fail. For manimgx the risk is sharper than usual: the integration corpus compares frames by the hash of their pixels, so any change in how a glyph is laid out or a color is rounded shows.
So everyone needs the same Python, the same version of every package and crate, and the same tools with the same settings, in one command:
A bug from six months ago? Check out that commit and run just sync: its Python packages
and Rust crates come back as they were. The system's libraries and the Rust toolchain are
not pinned.
The rest of this page describes the files that make this work.
Files in the root¶
pyproject.toml¶
The package (see The package), the workspace it is developed in, and what developing it takes. uv manages the workspace as one:
[tool.uv.workspace]: its other members, the font packages infonts/, each with apyproject.tomlof its own (seefonts/). One lockfile holds them all, and one environment,.venv, has them all installed.[tool.uv.sources]: the members as they are here. manimgx requires its font packages at exact versions; in the workspace those are the ones infonts/, installed editable, like manimgx, whose engine uv builds.[dependency-groups]: what developing needs.devholds the tools (ruff, ty, pytest and its plugins, prek), FastAPI for the review panel, and PyAV, with which the corpus writes and reads its videos.docsholds Zensical and mkdocstrings.ceholds Manim Community Edition, which renders the corpus's Manim references (see Setup). Each is installed with manimgx.[tool.uv]:devanddocsare installed by default.- The tools' settings:
[tool.pytest],[tool.coverage.run](see Testing),[tool.mutmut],[tool.fastapi], and those of the tools that keep the code's form and check its types,[tool.ruff]and[tool.ty](below).
uv.lock¶
The exact version of every Python package the project uses, down to the dependencies of
dependencies. just sync installs exactly these, not the latest ones.
Never edit it by hand: after changing a dependency in a pyproject.toml, run just lock,
and commit both files. just upgrade moves every package to the newest version the
pyproject.toml files allow. The recipes run their tools with uv run --frozen, which uses
the lockfile as it is, without checking it against the pyproject.toml files.
justfile¶
just runs commands defined once in a file. Without it, everyone types the same long commands with different options. With it:
just test # pytest, in parallel, in the locked environment
just check # every check of .pre-commit-config.yaml
just serve-docs # render the docs' examples, write the reference, serve the site
The recipes are grouped (development, testing, docs, release), and a comment above a recipe
is its description in just --list. The GitHub workflows run these same recipes, so a
task does the same on a developer's machine as in CI. Setup lists
every recipe.
ruff¶
ruff keeps the code's form the same everywhere:
- it formats, in its stable style and 88 columns: the Python, the examples in the docstrings, and the Python in the Markdown. It does not split a long string.
- it lints: it finds likely bugs, unsorted imports and outdated idioms.
[tool.ruff]inpyproject.tomlselects its rules and gives the reason for each one it ignores; the package's re-exports in__init__.pymay import with*and import names they do not use.
just format runs ruff's automatic fixes, then its formatter. just check runs both too
(below). Docstrings have a form of their own, the Google style, since the API reference is
generated from them: see Documentation.
ty¶
ty checks types. manimgx is typed throughout, and ty reports
nothing on it: just check must pass with no errors, and a change keeps it that way. Two
rules follow:
- No
Anyin a public signature. Keyword options are aTypedDict, taken as**kwargs: Unpack[…], never as**kwargs: Any, so a misspelled keyword fails the check, in manimgx and in a user's scene alike.Style, indrawing/paint.py, is the style keywords;Polygontakes**kwargs: Unpack[Style]. - Only live suppressions, in ty's form. ty honors
# ty: ignore[rule]and a bare# type: ignore, not# type: ignore[code]. A suppression that suppresses nothing is an error ([tool.ty]makes it one), so one that a fix has made useless fails the check. ruff's formatter does not count a trailing suppression comment in a line's width, but it splits a line too long without one and moves the comment to the last line, so runjust checkafterjust format.
[tool.ty] is the one list of what ty checks: the packages, the docs' scripts, the corpus's
tools and review server, and the tests it names. The hook that runs ty passes it no files,
so just check checks that list. It also reads svgelements as untyped (ty cannot read its
source's encoding).
A user's scene is held to more: every corpus case type-checks with every rule, and no
value manimgx gives it may be Any (see Testing).
.pre-commit-config.yaml and just check¶
Every check the repository has, as hooks:
- file checks: no merge conflict markers, valid TOML and YAML, a final newline, no trailing whitespace;
- check-jsonschema, which checks
CITATION.cffagainst the Citation File Format's schema; - zizmor, which finds security problems in the GitHub workflows;
- ruff and ty, run from the environment (
uv run --frozen), so they are the versionsuv.lockpins; - TypeScript 7 and Prettier for the browser package, run with the versions its
bun.lockpins; - reuse, from the environment too, which checks that the license and the copyright of every file are known (see Licenses).
prek, a fast implementation of pre-commit, runs them: just check
is prek run --all-files --show-diff-on-failure. A hook that changes a file (ruff fixing
or formatting it, a final newline added) fails the check, shows the change,
and leaves the file changed; the next just check passes if nothing else is wrong.
The repository installs no git hooks: the checks run when you run them, and on every push
to main and every pull request, in the Test workflow (see
GitHub workflows).
Licenses¶
manimgx is under the MIT License, in
LICENSE: GitHub, PyPI and
npm show it. Some files are others': modules ported from Manim CE, the Noto fonts, the
corpus's scenes from CE's documentation, the command spec made from mitex's Typst package, a
few documents and images. The repository says whose each file is, and under which license, as
REUSE specifies:
- a file with code from elsewhere says it in its first lines: an
SPDX-FileCopyrightTextline for each copyright holder (manimgx's,2026 Academa, Inc., among them) and anSPDX-License-Identifierline, as the modules ported from Manim CE do; REUSE.tomlsays it for the rest: manimgx's own files, in one annotation; the fonts, the corpus, data and images;LICENSES/holds each license's text, once, named by its SPDX identifier.
The license texts the engine's crates keep for the sources they fetch (COPYING, LICENSE;
see Others' sources) are their projects', as published: REUSE
leaves files of those names be. reuse lint, a hook of just check, fails while a file's
license or copyright is unknown, or a license has no text. A new file of manimgx's needs
nothing; code brought from elsewhere needs its lines; data or an image needs an annotation.
What a wheel holds that others hold the copyright in comes with its notices (see
The package).
A wheel holds FFmpeg's audio decoders, under the LGPL-2.1-or-later, and every wheel but the
browser's holds x264, under the GPL-2.0-or-later, with crates under the Apache-2.0 alone
(Typst, mitex), which only the GPL's third version takes in: such a wheel, as a whole, is under
the GPL-3.0-or-later. Its section 6 (and the LGPL's, which refers to it) asks
that whoever gets the wheels can get their complete source, from where the wheels are or from
a place the wheels point to, for as long as the wheels are offered: all of it, the crates too,
not links to where others publish them, which can go. Each GitHub release carries it,
manimgx-X.Y.Z-source.tar.xz, which builds the wheels offline (see
Others' sources); the wheels' notice, the README (PyPI's page) and
the release's notes point to it, and the README and the notes say what FFmpeg asks every page
that offers it to say. A release is never deleted while PyPI offers its wheels.
scripts/¶
What writes and builds, a folder for each thing it makes:
release/: the release's scripts, which the recipes and cibuildwheel run.create_executable.py(just create-executable) makes this machine's executable from its wheel indist/: a standalone Python (3.14, through uv) with the wheel, the font packages and the dependenciesuv.lockpins installed, packed into one file by PyApp. The file's first run unpacks the Python it holds; every run then runsmanimgxthere, with no network and no Python on the machine, andmanimgx selfmanages it (self pip installadds a package a scene imports). The script writesdist/manimgx-<os>-<arch>.tar.gz(a.zipon Windows), after running what it made once.smoke_test.pyrenders a scene with an installed manimgx: the check a wheel or an executable passes before it is kept.build_lavapipe.shbuilds Mesa's lavapipe for a Linux wheel, in the manylinux image (cibuildwheel'sbefore-all): Mesa and glslang from their sources, checked against pinned hashes, with the image's LLVM linked in, exporting only what Vulkan's loader calls. It writessrc/manimgx/lavapipe/libvulkan_lvp.so, which the wheel then takes in.licenses.py(just licenses) writes the wheels' third-party notice (see The package).docs/: the docs' generated pages, whichjust build-docswrites before the site is built: the Gallery and the API reference (see Documentation).showcase/: the README's pictures, made by hand and committed: the logo (the banner, the header's, the favicon) and the wall of example films.engine/: what the engine is compiled with, made by hand:dfg_table.pywritesrust/engine/src/dfg.bin, the split-sum table a lit surface's specular reflection is read from, run again only if the lighting changes.benchmark/: manimgx against Manim CE, ManimGL and Blender on the README's scenes, and the README's chart from its results.
The docs' and the coverage's hosting¶
docs/zensical.toml holds the
docs site's settings (see Documentation); the site is built into
docs/site/. Hosting is configured alongside
the workflows, in .github/deploy/:
docs.jsonc serves the docs site, and coverage.jsonc serves the tests' coverage report
(see Testing).
AGENTS.md and the contributing guide¶
AGENTS.md is this guide's commands, rules and layout in brief, for coding agents; the
assistant of the AI workflow
is told to follow it. The
contributing guide
says how a change is proposed (an issue first, then a pull request) and links here for how
the project works. When a command or a rule changes, AGENTS.md changes with this guide.
.gitignore¶
What is made, not written, stays out of git: the environment, the compiled engine, rust/target/, dist/, the built site, the docs' rendered films and generated reference, the coverage data and report, and render output (media/, *.mp4). The corpus's reference videos are .mkv files and are ignored by Git; their SHA-256 sidecars are committed.
The package¶
The repository's root is the package, defined in
pyproject.toml, in the
standard format:
[project]: the name, version, supported Pythons (3.13 and 3.14), license, and the packages manimgx needs at run time. Understanding manimgx says what each is for.licensenames every license of what the distributions hold, as one SPDX expression: manimgx's own MIT, those of the crates compiled into the engine (x264's GPL-2.0-or-later among them), of Typst's fonts and data, and of the lavapipe a Linux wheel bundles.license-filesputsLICENSEand the two notices beside it into the wheel's metadata. Its README is the repository's,README.md.[project.scripts]: makesmanimgxa command, runningmanimgx.cli:main.[build-system]and[tool.maturin]: maturin builds the package. It compiles the crate inrust/engine/into the modulemanimgx._engineand puts it beside the Python sources fromsrc/.[tool.uv]:cache-keyssays when the engine is rebuilt (below).[tool.cibuildwheel]: how the wheels are built and tested (Distribution). A wheel is tested with the fonts it is released with, this repository's, which PyPI may not have yet: they are installed before it.
Beside it, at the root, the notices license-files names besides LICENSE:
LICENSE-THIRD-PARTY: what the wheels hold that others hold the copyright in, with the licenses and notices it comes with: the package's files that name others (the modules ported from Manim CE), with their copyright lines and their licenses' texts fromLICENSES/; and every crate compiled into the engine, in each build a wheel holds (each platform's extension, Pyodide's, the player for a page), with the license and notice files it ships, the workspace's crates those of what they fetch (x264's, FFmpeg's, libopus's, mitex's).just licenseswrites it again afterCargo.lockchanges (so doesjust upgrade), or after code carrying third-party notices moves between files. A test fails while the notice is stale, or while a license in it is not among manimgx's.LICENSE-LAVAPIPE: the notices of the lavapipe the Linux wheels hold: Mesa's, with the copyright lines of the sources it is built from, and those of what it holds, of the libraries it links to and of LLVM. It is written by hand, from Mesa's sources: a test fails whenscripts/release/build_lavapipe.shbuilds another Mesa than the one it describes.
In src/manimgx/, besides the Python:
py.typed: marks the package as typed, so type checkers read its annotations._engine.pyi: the engine's interface, typed (see The engine).cli/present.html: the page that presents a film's slides in a browser (manimgx present).
Rebuilding the engine¶
uv installs manimgx in editable mode: a change to a Python file applies the next time
Python runs. The engine is different: it is a compiled file (_engine.abi3.so in
src/manimgx/ on macOS and Linux, ignored by git) that exists only once maturin has built
it.
uv rebuilds a package when one of its cache keys changes. cache-keys lists the engine's
sources: the crates' Rust files and shaders (*.wgsl, compiled into it), their manifests
and build scripts, the C shims, the lockfile, and mitex's lib.typ and command spec. What the
crates fetch changes only with their build scripts, which pin it.
uv run brings the environment up to date before it runs anything, so after an edit to
rust/engine/src/vector.wgsl, the next just test rebuilds the engine, then tests it.
The build is maturin's release build, optimized as [profile.release] in rust/Cargo.toml
says (full optimization, thin link-time optimization): slower to compile, fast to run. A file the engine is built from that no pattern in cache-keys matches needs an
entry of its own, or its changes are not seen. uv sync --reinstall-package manimgx
rebuilds the engine whatever changed.
fonts/¶
The font packages, the workspace's other members:
manimgx-fonts/ and
manimgx-fonts-cjk/,
each with its manifest, its README and its license; what each holds is what its users get.
The Noto fonts text is set in, besides Typst's own, so that text is the same on every
machine: Noto Sans and the faces for the scripts Latin faces lack in manimgx-fonts, Noto
Sans CJK in manimgx-fonts-cjk, each under the SIL Open Font License (LICENSE). They are
data, 40 MB of it, and change rarely: packages of their own, built by uv's backend into one
wheel for every platform, and released when they change, not with every manimgx, which
requires them at exact versions. A version is the date the fonts were taken from Noto.
manimgx-fonts-cjk is not required in the browser, where 33 MB is too much for a page to
download. manimgx finds the fonts in them (importlib.resources), in whichever are
installed.
rust/¶
The engine's crates, as one workspace: rust/Cargo.toml
lists them (every folder here), the build profiles they are compiled with, and the patch that
gives mitex manimgx's own spec crate; Cargo.lock is to Rust what uv.lock is to Python, every
crate's exact version, committed (just upgrade moves it too). Cargo runs here, and builds into
rust/target/. The engine says what the crates do:
engine/: the engine,manimgx._engine— with the player, in a window natively, and in Pyodide carrying the player for a page; its manifest holds its dependencies (wgpu, PyO3, Typst, mitex, winit, rustybuzz) and the Clippy lints it answers to.x264/,nasm/: x264 and NASM, built from their sources.ffmpeg/: FFmpeg's audio decoders and libopus, built from their sources, behind one call.mitex-spec-gen/: mitex's Typst package, which the engine serves to Typst (with its ownlib.typ, which imports only the scope the converted LaTeX calls), and the command spec made from it.fetch/: what the others' build scripts fetch with: a source tree, pinned by the SHA-256 of its files (see The engine).
The repository holds none of what others wrote but its license texts, which the crates keep
for the notices (COPYING, LICENSE), as their authors published them; the checks leave them
alone.
browser/¶
manimgx for the browser, published to npm as manimgx: the player's element (src/player.ts)
and the director's worker (src/director.ts). just build-npm compiles the worker before
embedding it in one browser module (dist/manimgx.js), and TypeScript 7 generates the public
declarations from the implementation. The package installs manimgx's wheel for the browser
from PyPI, at its own version, in Pyodide, and plays with the player that wheel's engine carries.
The development tools are pinned in bun.lock. just check checks the TypeScript and its
formatting; just test-typescript runs the package's tests, in browser/tests/.
docker/¶
The Dockerfile of an image that installs manimgx from PyPI (the version it is built with,
or the latest) into python:3.14-slim, with Vulkan's loader and Mesa's drivers: lavapipe
draws on the CPU, and a GPU given to the container (--device /dev/dri for AMD and Intel,
--gpus all for NVIDIA) is used instead. Its entry point is the manimgx command, run in
/work:
just build-docker-image <version> builds it; a release publishes it.
Releases¶
The version is in pyproject.toml (0.1.0; the engine's crate carries the
same number). manimgx follows Semantic Versioning, and its
changelog Keep a Changelog: a change a
user would notice adds a line under "Unreleased" (see
Documentation).
A release takes three steps:
- A commit on
mainsets the new version inpyproject.toml, and moves the changelog's "Unreleased" lines into a section of their own,## X.Y.Z. just release, at that commit, asks for confirmation, tags itvX.Y.Z, and pushes the tag.- The tag starts the Release workflow. It
stops unless the tag is manimgx's version and the changelog has a section for it; then
it runs the tests, builds everything, and publishes: the GitHub release, whose notes are
the changelog's section (
just release-notes X.Y.Zprints them), PyPI, npm and ghcr.io. PyPI gets the font packages' versions it doesn't have yet: a change to the fonts sets a new version in theirpyproject.tomland in manimgx's requirement, and is released with the next manimgx.
A version that ends in aN, bN or rcN (0.2.0rc1, say) is a pre-release.
Learn more¶
- uv's documentation: projects, lockfiles, dependency groups.
- maturin's user guide: mixed Rust and Python projects.
- cibuildwheel's documentation: building and testing wheels.
- just's manual.