Skip to content

feat(diff): --latexdiff and --pdf for marked-up revisions - #56

Draft
Waynting wants to merge 1 commit into
aloth:mainfrom
Waynting:feat/latexdiff
Draft

feat(diff): --latexdiff and --pdf for marked-up revisions#56
Waynting wants to merge 1 commit into
aloth:mainfrom
Waynting:feat/latexdiff

Conversation

@Waynting

@Waynting Waynting commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Draft PR for --latexdiff and --pdf from #55 — the follow-up left open when the core diff command shipped in 0.10.0.

Opening it as a draft because design question 1 in #55 is still yours to settle: --pdf cannot compile without writing to the project. Everything below implements the option I leaned to there (a temp file in the project, cleaned up); if you would rather it created a scratch project or compiled locally, the change is contained to one function.

What it does

olcli diff --latexdiff          # write .olcli-diff/main-diff.tex
olcli diff --latexdiff --pdf    # ...and compile it, download .olcli-diff/main-diff.pdf

It runs on the two sides diff has already fetched, so the markup means exactly what the patch output means — struck through is what a push would overwrite, underlined is what it would upload — and it costs no extra request.

The decisions from #55, and what testing changed about them

--pdf uploads, compiles, deletes. The compile endpoint takes a rootResourcePath that must already be in the project, so there is no way to compile a document that is not in it. The markup goes up as olcli-latexdiff.tex next to the root document, and the command says so before it does it:

  uploading olcli-latexdiff.tex to "my project" for one compile, then removing it

Three properties are structural rather than incidental, and each is exercised below:

  • It refuses rather than overwrites. If that path is taken, the file belongs to the user.
  • The delete runs from a finally, and nothing between the upload and it calls process.exit — that would terminate before the cleanup and leave the file on the project. Failures are collected and reported after the cleanup instead.
  • An interrupt cannot be cleaned up after reliably, so SIGINT prints the exact olcli rm command rather than leaving the file to be discovered.

--flatten by default. Without it the markup still contains \input{sections/intro}, and a remote compile resolves those against the files in the project — the old content — producing a PDF that marks up the root document and shows every input file as unchanged. Wrong in a way that is hard to notice. latexdiff --flatten resolves each \input relative to its own side, so pointing it at the extracted remote tree and the working directory needs no cwd juggling.

Output goes to .olcli-diff/. scanLocalFiles skips dotted entries before any ignore rule is consulted, so this is the one place output cannot leak into a later push. A plain main-diff.tex next to the document would be uploaded on the next sync.

Ambiguity is reported, not guessed. The root document is the .tex declaring \documentclass; several candidates are listed and --main is asked for. Preferring main.tex would silently mark up the wrong document in a project that also holds a poster, which produces a plausible PDF describing the wrong revision.

One thing in #55 I had wrong, found by testing it. I wrote that a figure existing only locally would fail the --pdf compile. It does not: Overleaf draws a placeholder box naming the missing file and reports success. What actually fails is a .sty or .cls that only exists locally, because the compile runs against the project. The message, the README and the changelog now say that instead, and the failure path also deletes a PDF left by an earlier run rather than leaving one dated now that describes a different revision.

Rejected flag combinations, before connecting

--latexdiff with --name-only, --file or -U is refused, as is --main/--output/--latexdiff-opt without --latexdiff — accepting those silently would print a normal patch with nothing saying a flag had been dropped. All of it is checked before the login and the project download, so a typo costs no requests.

--latexdiff-opt

One addition to the flag list in #55: --latexdiff-opt --math-markup=0 passes options straight through. Without it there is no way to tune latexdiff at all, because the remote tree exists on disk only for the duration of the run and cannot be handed to the tool by hand afterwards. Arguments go through execFile as an argv array, never a shell.

Structure and tests

src/latexdiff.ts holds root document detection, argument construction, output naming, tree materialization and failure interpretation as functions over data — 21 unit tests, no Overleaf account and no latexdiff binary required. 79 tests total under npm test. npm run lint adds no new warnings to the budget (62 before and after; the module itself contributes none).

docs/ARCHITECTURE.md gets the new row, and its module-map heading is corrected: that column claimed "no filesystem", which scan.ts already contradicted and latexdiff.ts — the one module that shells out to something olcli does not ship — would have contradicted further.

test/e2e.sh gains a latexdiff section between the diff and push tests, skipped with a warning when latexdiff is not on PATH. It uploads a root document of its own so it does not depend on what the target project contains, checks the markup strikes through the remote wording and underlines the local one, checks the output is invisible to push --dry-run, and checks --pdf cleaned up after itself — using diff --name-only, since a leftover scratch file is exactly what diff reports as remote-only.

Verified against a real project

npm run lint, npm run build, npm test and the entry-point check all pass. Beyond that I ran the feature by hand against a throwaway Overleaf project created from the example template — main.tex, sample.bib and frog.jpg, so the compile had a figure and a bibliography to resolve:

  • --latexdiff on an edited main.tex produced the markup; latexdiff's own warning about not flattening \bibliography was passed through rather than swallowed, and is harmless here because the remote compile has sample.bib.
  • --pdf returned a 272 KB PDF with the title, abstract and introduction changes struck through in red and underlined in blue, the frog figure and the hyperlinks intact.
  • diff --name-only afterwards reported 0 remote-only, so the scratch file was gone; push --dry-run listed only main.tex, so .olcli-diff/ stayed invisible to push.
  • Uploading a file named olcli-latexdiff.tex by hand and re-running --pdf was refused, and the file came back byte-identical.
  • Adding a locally-only .sty made the compile fail: exit 1, the CLSI log written to .olcli-diff/main-diff.log with ! LaTeX Error: File 'mylocalstyle.sty' not found., the stale PDF removed, and the scratch file still deleted from the project.

I have not run the full test/e2e.sh; the account I tested with has no olcli test project, and I did not want to point the suite at a real paper.

Follow-ups, not in this PR

Refs #55

Follow-up to aloth#45, left open when the core diff command shipped in 0.10.0.
A unified diff is the right artifact for a developer and the wrong one for
a thesis advisor, who expects the revision marked up in the document.

--latexdiff runs on the two sides diff has already fetched, so the markup
means what the patch output means and costs no extra request. --pdf
compiles it with Overleaf's compiler, so a reviewable PDF needs no local
TeX installation.

Root document detection, argument construction, output naming and failure
interpretation are functions over data in src/latexdiff.ts, unit-tested
with no Overleaf account and no latexdiff binary.

Refs aloth#55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant