phucnht

Why I built texchanges

Tuesday, August 18, 2026 · 3 min read

A friend doing a PhD complained that reviewing papers on Overleaf is worse than using track changes in Word. I assumed this was a habit problem and went to check. It is not a habit problem.

What is actually missing

Overleaf has a comment panel, and it is fine for a handful of remarks. It gets thin quickly once the editing is heavy, because comments sit beside the document rather than in it, and there is no way to say “this sentence should become that sentence” as a reviewable object.

The usual answer is latexdiff. It produces good word-level diffs, but you have to configure latexmkrc to wire it up, which is a real barrier for people whose job is the paper and not the toolchain. It also compares two finished snapshots, so it has nothing to say during drafting, and no place to put a reviewer ID or an inline note.

The changes package is closer to what my friend wanted. It handles simple review flows well. What it does not carry is a decision: whether a proposed edit was accepted or rejected, and by whom, tracked in the file itself.

What I ended up writing

texchanges keeps the change in the source and attaches a decision to it.

\txreplace[author=phuc, id=R12, status=pending]{old text}{new text}

A LaTeX page compiled in review mode. Replaced text is struck through in red with the replacement underlined in blue, each change carries a superscript reviewer name, and a generated summary below counts the changes per author.

Review mode: the markup renders in place, and the summary is generated from it.

The package option picks what the same file compiles into: review shows the markup and a generated report, final reads as the accepted document, and original recovers the pre-review text. Nothing forks, so a reviewer and an author can work from one file and see different documents.

The same LaTeX source compiled in final mode. Every proposed edit has been applied and no markup is visible, so the page reads as an ordinary paper.

Final mode, from the same source file.

Two other pieces came out of watching how people actually finish a review. There is a bundled Overleaf setup so the automatic latexdiff path works without hand-editing latexmkrc, and a texchanges-merge CLI that resolves accepted changes back into clean LaTeX when the review is done, so the markup does not accumulate across rounds.

The automatic diff path, showing the original and revised documents beside the generated word-level diff of the two.

The automatic path, produced by the bundled Overleaf configuration.

Two decisions worth explaining

The merge CLI does not call TeX. It reimplements the markup grammar in Python, hand-rolling brace matching and skipping comments and verbatim environments. Shelling out to LaTeX would have been less code, but the tool is meant to run on a laptop in a second and to work on files that do not compile yet, which is often the state a document is in mid-review. It also uses only the standard library, and a test enforces that import list, because TeX Live ships it as a single script and cannot pull in dependencies.

The other decision was to freeze \txreplace{old}{new} in that order. The changes package uses the opposite order for \replaced, so the compatibility layer that accepts its commands is opt-in rather than automatic. Silently swapping two arguments that are both prose would be very hard to notice and very annoying to debug.

Where it is

It is on CTAN as version 0.3.0, so tlmgr install texchanges works, and the documentation is at texchanges.dev. CI checks it against TeX Live 2023 onwards on pdfTeX, XeTeX, and LuaTeX, since Overleaf pins TeX Live by year and those are the versions people are actually on.

If you write papers in LaTeX with someone else, I would like to hear what breaks. The project page has more detail, and the source is on GitHub.