Introduction
This guide walks you through converting an academic thesis (PhD, HDR, or similar) into a modern Typst project with:
- Bilingual PDF output – French and English from a single source.
- HTML website – a responsive, searchable, multi-chapter site.
- GitHub Pages deployment – automated build and publish on every push.
The techniques were developed while converting two real theses:
| Project | Source | Live site |
|---|---|---|
| PhD thesis (PageRank) | balouf/phd-pagerank | balouf.github.io/phd-pagerank |
| HDR thesis (P2P) | balouf/hdr-p2p | balouf.github.io/hdr-p2p |
Browse them to see what the end result looks like – bilingual toggle, chapter navigation, full-text search, downloadable PDFs.
Modular structure
Each section of this guide is self-contained. Pick the ones you need:
| Section | You need this if… |
|---|---|
| LaTeX to Typst | Your thesis is currently in LaTeX |
| Bilingual PDF | You want French + English output |
| HTML Export | You want a web version of your thesis |
| GitHub Deployment | You want automated build + hosting |
| Working with an LLM | You want to use AI assistants effectively |
Already have a Typst project? Skip section 1. Monolingual thesis? Skip section 2. Only need PDF? Skip sections 3 and 4.
Prerequisites
- Typst >= 0.14.2 – HTML export requires a recent version. Install from
typst.app or via
cargo install typst-cli. - Python >= 3.11 – used by the build script that splits HTML into chapters.
- uv – the fast Python package runner. Install from
docs.astral.sh/uv. The build script uses
uv runto manage its own dependencies automatically. - Git and GitHub – basic familiarity with repositories, branches, and GitHub Actions.
The boilerplate
The boilerplate/
directory in this repository contains ready-to-use template files:
boilerplate/
main.typ # PDF entry point
main-html.typ # HTML entry point
deploy.yml # GitHub Actions workflow
templates/
i18n.typ # Bilingual support (#t(), lang variable)
meta-data.typ # Thesis metadata (title, author, jury)
html-overrides.typ # Show rules for HTML export
prelude.typ # Common imports for chapters
chapters/
example.typ # Example chapter (French)
example.en.typ # Example chapter (English)
web/
build.py # HTML build script (split, nav, search)
assets/ # CSS and JS for the website
Copy the boilerplate into your own repo and follow the TODO: adapt comments
in each file. The guide sections below explain the reasoning behind each piece.
Section overview
Section 1 – LaTeX to Typst Strategy and tooling for converting a LaTeX thesis to Typst. When to let an LLM do the heavy lifting and when to review by hand.
Section 2 – Bilingual PDF
The i18n.typ architecture: the #t() function, when to use it vs. separate
.en.typ files, and the translation workflow.
Section 3 – HTML Export
How Typst’s HTML backend works, the show rules in html-overrides.typ, and
the build.py script that turns a single HTML blob into a navigable website.
Section 4 – GitHub Deployment Setting up GitHub Actions to compile Typst, run the build script, and deploy to GitHub Pages automatically.
Section 5 – Working with an LLM Practical advice for using large language models as coding partners throughout this workflow – what they excel at and where human judgment is essential.
LaTeX to Typst
This section covers converting a LaTeX thesis to Typst. If your thesis is already in Typst, skip ahead to Bilingual PDF.
When and why convert
There are several good reasons to move from LaTeX to Typst:
- Bit rot. Old LaTeX sources often stop compiling after a few years – packages vanish, engines change, build toolchains break. Typst is a single binary with no dependency hell.
- HTML export. Typst >= 0.12 has native HTML output. LaTeX-to-HTML pipelines (tex4ht, LaTeXML, Pandoc) are fragile and rarely produce clean results.
- Modern tooling. Incremental compilation, built-in package manager, readable error messages, and a scripting language that feels closer to Python than TeX macros.
You do not need to convert if you only need the original PDF. This process is worthwhile when you want to maintain, extend, or republish the thesis.
Strategy: chapter by chapter
Do not try to convert the entire thesis in one pass. Work incrementally:
-
Create the skeleton. Set up
main.typwith page layout, fonts, and imports. The boilerplate’smain.typis a good starting point. Get it to compile (even if empty). -
Convert one chapter at a time. Pick the simplest chapter first to establish patterns. Copy the LaTeX source into a
.typfile, convert the markup, compile, and fix errors. Once it looks right, move to the next. -
Keep the LaTeX source. Don’t delete the original
.texfiles until the entire thesis compiles in Typst. You will refer back to them constantly. -
Validate the PDF. After each chapter, compare the Typst PDF against the original LaTeX PDF. Check that equations render correctly, figures are placed, and cross-references resolve.
Who does what
The conversion is a collaboration between you, an LLM, and Typst packages.
LLM: bulk markup conversion
Large language models are excellent at the mechanical part of LaTeX-to-Typst conversion. Give the LLM a LaTeX chapter and ask it to produce the Typst equivalent. Be explicit in your instructions:
- Preserve all
\label{}as Typst labels (<label-name>). - Preserve all
\ref{}/\cite{}as Typst references (@label-name). - Keep all math content exactly as-is (only change LaTeX math syntax to Typst).
- Maintain the same section/subsection structure.
An LLM can typically convert a 20-page chapter in a single pass with ~90% accuracy. The remaining 10% is where human review matters.
Human: review and judgment
After the LLM produces a draft, review carefully:
- Mathematics. LLMs sometimes subtly alter mathematical notation – swapping a subscript, dropping a superscript, changing a symbol. Compare every equation against the original. This is the highest-risk area.
- Cross-references. Verify that labels and references compile without warnings. Typst will tell you about unresolved references.
- Editorial decisions. Some LaTeX constructs have no direct Typst equivalent. You decide how to handle custom environments, unusual layouts, or legacy formatting.
Typst packages
Use established community packages instead of reimplementing LaTeX environments:
| LaTeX | Typst package | Notes |
|---|---|---|
amsthm / thmtools | ctheorems | Theorem, lemma, definition, proof |
algorithm2e / algorithmicx | algo | Pseudocode algorithms |
glossaries / acronym | acrostiche | Acronym management |
subcaption / subfig | subpar | Subfigures with individual captions |
Check the Typst Universe for other packages you may need.
Common pitfalls
Bibliography
Typst supports two bibliography formats:
.bib(BibLaTeX/BibTeX) – if you already have a.bibfile, just use it directly. This is the path of least resistance..yml(Hayagriva) – Typst’s native format. Cleaner, but converting from.bibis rarely worth the effort for an existing thesis.
Point to your bibliography file in main.typ:
#bibliography("references.bib", style: "springer-lncs-alphabetical.csl")
CSL style files let you match the citation style of your original thesis. Download the right one from the CSL repository.
Figures
- PDF figures from LaTeX work directly in Typst. If your LaTeX project
generated figures as
.pdffiles, copy them over and useimage("figures/foo.pdf"). - EPS files need conversion to PDF or SVG first. Use
epstopdfor Inkscape for batch conversion. - TikZ figures do not exist in Typst. Either keep the compiled PDF output from LaTeX, or redraw with Typst’s drawing primitives or the CeTZ package.
French ligatures (oe, ae)
French text contains ligatures like “œuvre” or “nœud”. LaTeX handles these through font ligatures, but Typst needs the actual Unicode characters.
The boilerplate defines shorthand variables in prelude.typ:
#let oe = "œ"
#let OE = "Œ"
#let ae = "æ"
#let AE = "Æ"
Use them inline with #{} syntax: n#{oe}ud, c#{oe}ur, #{oe}uvre.
You can also type the Unicode characters directly (œ, æ) if your keyboard
layout supports it.
Why not a global show rule? A blanket
show "oe": "œ"would produce false positives: “coefficient” → “cœfficient”, and every English “oe” (does, goes, poem…) would be corrupted. Explicit characters avoid this entirely.
Encoding
Typst is strictly UTF-8. Old LaTeX files may use Latin-1 or other encodings. If you see garbled characters, convert the source file:
iconv -f latin1 -t utf-8 chapter.tex > chapter-utf8.tex
Then feed the UTF-8 version to the LLM for conversion.
Equation numbering
LaTeX and Typst number equations differently. LaTeX typically numbers by section (e.g., Equation 3.2), while Typst numbers sequentially by default.
You can configure Typst’s equation numbering, but don’t waste time trying to reproduce the exact LaTeX numbering. Readers of the new version don’t care about matching the old equation numbers – they care about internal consistency.
Reference projects
Both reference projects started as LaTeX theses and were converted to Typst following this approach:
- balouf/phd-pagerank – PhD thesis, ~200 pages, heavy on linear algebra and graph theory.
- balouf/hdr-p2p – HDR thesis, ~80 pages, combinatorics and distributed systems.
Browse their Git history to see the conversion unfold chapter by chapter.
Bilingual PDF
This section explains how to produce French and English PDFs from a single Typst project. If your thesis is monolingual, skip to HTML Export.
Architecture: i18n.typ
The bilingual system lives in templates/i18n.typ (included in the
boilerplate). It defines two things:
#let lang = sys.inputs.at("lang", default: "fr")
#let t(fr, en) = if lang == "en" { en } else { fr }
lang– read from the command line via--input lang=fror--input lang=en. Defaults to French.#t(french, english)– the core bilingual function. Pass two values; the one matching the current language is returned.
Compile with:
typst compile --input lang=fr main.typ thesis-fr.pdf
typst compile --input lang=en main.typ thesis-en.pdf
Import lang and t wherever you need them:
#import "templates/i18n.typ": lang, t
When to use #t() vs. separate files
There are two strategies for bilingual content, and you will use both.
Inline #t(): short, mixed content
Use #t() for anything where both languages fit naturally on the same line
or in the same block:
// Headings
= #t([Introduction], [Introduction])
// Figure captions
#figure(
image("figures/graph.pdf"),
caption: t([Graphe de convergence.], [Convergence graph.]),
)
// Metadata
#let thesis-title = t(
[#smallcaps[Analyse des réseaux pair-à-pair]],
[#smallcaps[Analysis of Peer-to-Peer Networks]],
)
// Table of contents
#outline(title: t([Table des matières], [Table of Contents]))
This works well for labels, captions, UI text, and any short bilingual string.
Separate .en.typ files: chapter prose
When a chapter is mostly prose (paragraphs of text with embedded math and
references), maintaining both languages interleaved with #t() becomes
unreadable. Instead, create a parallel English file:
chapters/
ch1.typ # French prose
ch1.en.typ # English prose
ch2.typ
ch2.en.typ
In main.typ, switch between them:
#if lang == "en" {
include "chapters/ch1.en.typ"
} else {
include "chapters/ch1.typ"
}
The key insight: math, labels, figures, and references are the same in both
files. Only the surrounding prose changes. Compare the boilerplate’s
example.typ and example.en.typ to see this in practice.
Rule of thumb
If more than about 5 consecutive lines need translation, use a separate file.
Short labels and captions stay inline with #t().
Translation workflow
Step 1: Write the primary language first
Get the French (or English) chapter fully working – correct math, figures placed, cross-references resolving, no compilation warnings.
Step 2: Let an LLM translate
Give the LLM the complete .typ file and ask for a translation. Be explicit:
- Translate all prose to English (or French).
- Preserve all Typst markup exactly:
#import, labels (<...>), references (@...), math ($...$), function calls. - Do not rename labels or references.
- Preserve the
#importline at the top of the file.
An LLM can translate a 15-page chapter in seconds. The output is usually syntactically correct on the first try.
Step 3: Human review
After the LLM produces the translation:
- Specialized terminology. Academic fields have precise vocabulary. Verify that the LLM used the correct English (or French) terms for your domain.
- Mathematical prose. Sentences like “Let $f$ be a continuous function on $[0,1]$” need to read naturally in both languages. The LLM usually handles this well, but check.
- Cultural references. French academic conventions (e.g., “on montre que”) don’t always translate literally.
Step 4: Compile and compare
typst compile --input lang=fr main.typ thesis-fr.pdf
typst compile --input lang=en main.typ thesis-en.pdf
Open both PDFs side by side. Check that all figures, equations, and references appear in both versions.
Pitfall: smart quotes around math in HTML
This issue is specific to HTML export but surfaces during bilingual work.
Typst’s smart quote algorithm can misidentify the direction of quotation marks
when they appear adjacent to math ($...$). For example:
the "value of $x$" // may produce wrong quote direction in HTML
The fix is to use explicit Unicode quote characters:
| Language | Opening | Closing | Unicode |
|---|---|---|---|
| English | \u201C | \u201D | \u{201C} / \u{201D} |
| French | \u00AB | \u00BB | \u{AB} / \u{BB} |
In practice, this only matters for the handful of places where quotes sit next to math. Most prose is unaffected.
HTML Export
This chapter covers the full pipeline from Typst source to a navigable, multi-page HTML website. The key idea: Typst compiles your thesis into a single HTML blob, then a Python script splits it into one page per chapter with sidebar navigation, search, and cross-chapter links.
Dual Entry Points
Your project has two Typst entry points:
main.typ— PDF output. Includes page setup, headers/footers, table of contents, page numbers.main-html.typ— HTML output. Wraps each chapter in achapter-section()call sobuild.pyknows where to split. Applies HTML-specific show rules.
Both files import the same chapter files (chapters/*.typ), so the content is shared.
The difference is in the structural markup around them.
Compiling for HTML:
typst compile --features html --format html \
--input lang=fr --input html=true \
main-html.typ web/dist/full-fr.html
The --features html flag unlocks the target() function, and --input html=true sets
the _is-html flag that chapter files can read without needing --features html
themselves.
html-overrides.typ
This file (in templates/) provides three things:
chapter-section(id, body)
Wraps content in <section class="chapter" id="id"> for HTML; passes through
transparently for PDF. This is the marker that build.py uses to find and split
chapters:
#chapter-section("distribution")[
#include "chapters/distribution.typ"
]
The id must match the section_id in build.py’s CHAPTERS list.
part-marker(id, title)
Optional grouping headers. In HTML, emits a <section class="part"> with an <h1> that
appears in the sidebar as a part heading (e.g., “Part I: Content Distribution”). In PDF,
it does nothing — part pages are handled by the thesis style.
html-show-rules
A set of show rules applied only in HTML mode, using target():
- Headings are converted to semantic
<h1>–<h4>withidattributes derived from their labels. Chapter counters are reset at each<h1>. - Inline math is rendered as SVG via
html.frame()wrapped in abox(so it stays inline). - Block math is rendered as SVG via
html.frame(). - Grids (e.g., side-by-side figures) are rendered as SVG with constrained width
(
42em) to avoid oversized output. - Algorithms (figures of kind
"algorithm") are rendered as SVG at38emwidth to force line wrapping within the HTML content area.
Important caveat about target(): The target() function is only available when
compiling with --features html. Files included by both main.typ and main-html.typ
(i.e., your chapter files and shared templates) cannot call target() directly — it will
error during PDF compilation. Instead, use the _is-html flag from i18n.typ:
#import "i18n.typ": _is-html
#let my-function = if _is-html {
// HTML version
} else {
// PDF version
}
Only html-overrides.typ itself (which is imported exclusively by main-html.typ) may
use target().
Theorem Environments
The file environments.typ provides dual implementations for every theorem type
(theorem, lemma, proposition, corollary, definition, conjecture, remark, etc.):
- PDF mode: uses the
ctheoremspackage (thmbox) with colored fills and borders. - HTML mode: uses
_html-thm()to generate<div class="thm-box thm-theoreme">wrapped in afigure(for labeling and numbering).
Each environment type has a CSS class (e.g., .thm-theoreme, .thm-lemme,
.thm-proposition). The css-class argument in _html-thm() must match a rule in
style.css.
Common gotcha: if you add a new theorem type and forget the CSS rule, the box will render with no background and no border — effectively invisible. Always add both the light and dark theme CSS rules:
.thm-mytype { background: #f0f0f0; border-color: #999; }
[data-theme="dark"] .thm-mytype { background: #1e1e1e; }
build.py — The Build Script
The Python build script (web/build.py) orchestrates the full pipeline. Run it with:
uv run web/build.py # Build both languages
uv run web/build.py --skip-lang en # French only
uv run web/build.py --skip-pdf # Skip PDF compilation
uv run web/build.py --skip-compile # Reuse existing full.html (faster iteration)
The script performs these steps:
- Compile Typst to HTML — runs
typst compile --features html --format htmlfor each language, producing a singlefull-fr.html/full-en.html. - Parse and split — loads the HTML with BeautifulSoup, finds all
<section class="chapter">elements, and maps theiridto the chapter list. - Rewrite cross-chapter links — any
href="#some-id"pointing to an element in a different chapter is rewritten tohref="other-file.html#some-id". - Collect footnotes — Typst emits all footnotes as endnotes in a single section. The script distributes them back to their respective chapter pages.
- Generate pages — each chapter becomes a standalone HTML page with: a sticky topbar, a left sidebar (global navigation), a right sidebar (local table of contents from h2/h3 headings), and prev/next navigation links.
- Generate redirect index —
dist/index.htmldetects the browser language and redirects tofr/oren/. - Copy CSS/JS assets — copies
style.css,nav.js, and any other files fromweb/assets/intodist/assets/. - Run Pagefind — indexes the generated pages for client-side full-text search.
Configuration in build.py
The top of build.py has a configuration section marked with # TODO: adapt comments.
Here is what to customize:
CHAPTERS
A dict keyed by language, each value a list of (section_id, filename, title) tuples:
CHAPTERS = {
"fr": [
("cover", "index.html", "Accueil"),
("distribution", "distribution.html", "Distribution de contenu"),
("bibliography", "bibliography.html", "Bibliographie"),
],
"en": [
("cover", "index.html", "Home"),
("distribution", "distribution.html", "Content Distribution"),
("bibliography", "bibliography.html", "Bibliography"),
],
}
The section_id must match the id in the corresponding chapter-section("id") call in
main-html.typ. If they do not match, the script will print a warning and skip the
chapter.
PARTS
Groups chapters in the sidebar. Each entry is (title_or_none, [list_of_section_ids]):
PARTS = {
"fr": [
(None, ["cover", "introduction"]),
("Distribution", ["distribution", "caching"]),
(None, ["bibliography"]),
],
}
Use None for a flat group (no heading), or a string for a labeled group that renders as
a part header in the sidebar.
SUB_CHAPTERS
A set of section_id values that should appear indented and in a smaller font in the
sidebar:
SUB_CHAPTERS = {"acyclique-origine", "acyclique-bases", "acyclique-convergence"}
This is useful when decomposing a large chapter into multiple pages (see below).
Other settings
THESIS_TITLE— the title shown in the topbar, per language.GITHUB_URL— links to the GitHub icon in the topbar.BASE_URL— overridden by the--base-urlCLI argument; critical for GitHub Pages deployment.
CSS Theming
The file web/assets/style.css uses CSS variables for light and dark modes. The root
:root block defines light theme colors, and [data-theme="dark"] overrides them.
Key conventions:
.thm-*classes define theorem box appearance (background + left border).- SVG frames from
html.frame()get the class.typst-frame. In dark mode, they are inverted withfilter: invert(1) hue-rotate(180deg)to flip black-on-white to white-on-dark while preserving colors. - The layout is a CSS grid with three columns (left sidebar, content, right TOC) that collapses responsively at 1200px and 960px breakpoints.
The file web/assets/nav.js handles theme toggling (light/dark/auto cycle), sidebar
open/close on mobile, Pagefind search modal (lazy-loaded on first open), keyboard
shortcuts (Ctrl+K for search, Escape to close), and scroll-spy highlighting in the local
table of contents.
Decomposing a Large Chapter
If a chapter has natural sub-sections that would each make a long page (like the acyclic preferences chapter in hdr-p2p), you can split it into multiple HTML pages while keeping the single chapter structure in the PDF.
In main-html.typ, use separate chapter-section() calls for each sub-section:
#chapter-section("acyclique")[
#include "chapters/acyclique-html-intro.typ"
]
#chapter-section("acyclique-origine")[
#include "chapters/acyclique-origine.typ"
]
#chapter-section("acyclique-bases")[
#include "chapters/acyclique-bases.typ"
]
In main.typ, include the full chapter as a single unit (the PDF has no splitting).
Then in build.py, list the sub-sections in CHAPTERS and add their IDs to
SUB_CHAPTERS so they render indented in the sidebar. The parent entry
("acyclique") appears as a top-level item, and the sub-entries appear nested below it.
This gives readers a manageable page length in the browser while the PDF retains its natural flow.
GitHub Deployment
This chapter covers publishing your thesis website to GitHub Pages with automated builds.
Repository Structure
A typical repository layout:
your-thesis/
├── .github/workflows/deploy.yml
├── .gitignore
├── README.md
├── main.typ # PDF entry point
├── main-html.typ # HTML entry point
├── templates/ # Shared Typst templates
├── chapters/ # Chapter source files (.typ)
├── figures/ # Images and diagrams
├── web/
│ ├── build.py # Build script
│ └── assets/
│ ├── style.css
│ ├── nav.js
│ └── favicon.svg
├── references.bib
└── springer-lncs-alphabetical.csl
.gitignore
Essential entries to keep the repository clean:
# Build output
web/dist/
# PDFs (except figure PDFs)
*.pdf
!figures/**/*.pdf
# Typst cache
.typst-cache/
# Python
__pycache__/
.venv/
# OS
.DS_Store
Thumbs.db
deploy.yml
The GitHub Actions workflow (provided in boilerplate/deploy.yml) automates the full
build-and-deploy cycle. Key points:
- Triggers: push to
mainand manual dispatch (workflow_dispatch). - Permissions: needs
pages: writeandid-token: writefor GitHub Pages deployment. - Steps: checkout, install Typst (
typst-community/setup-typst@v4), install uv (astral-sh/setup-uv@v5), runbuild.py, upload artifact, deploy to Pages.
Three settings deserve attention:
--base-url is critical. GitHub Pages serves your site at
https://username.github.io/repo-name/, so all asset paths (CSS, JS, Pagefind index)
must be prefixed with /repo-name/. The build command in the workflow should read:
run: uv run web/build.py --base-url /your-repo-name/
If you forget this, the site will load with no styles and broken search.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true is set as an environment variable to
suppress Node.js deprecation warnings from older GitHub Actions.
enable-cache: false on setup-uv is necessary because build.py uses inline
script dependencies (the # /// script header) rather than a uv.lock file. With
caching enabled, uv would look for a lock file and fail.
Enabling GitHub Pages
Before the first deployment:
- Go to your repository on GitHub.
- Navigate to Settings > Pages.
- Under Source, select GitHub Actions.
That is all. The workflow will handle the rest on the next push to main.
Favicon
Create a simple SVG favicon in web/assets/. The <link rel="icon"> tag is generated
by build_page() in build.py, pointing to ../assets/favicon.svg.
A minimal SVG with colored background and text initials works well:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#2196F3"/>
<text x="16" y="22" text-anchor="middle"
font-family="sans-serif" font-size="16" font-weight="bold"
fill="white">FM</text>
</svg>
Replace the initials and color to match your identity.
README.md
Include at minimum:
- A “Read online” link:
https://username.github.io/repo-name/ - Build instructions for local development:
uv run web/build.py --base-url / open web/dist/index.html - PDF compilation instructions:
typst compile --input lang=fr main.typ thesis-fr.pdf typst compile --input lang=en main.typ thesis-en.pdf - License information (your thesis content license).
Deployment Checklist
When setting up a new thesis repository:
- Copy the boilerplate files into your project.
- Edit
build.py: setCHAPTERS,PARTS,SUB_CHAPTERS,THESIS_TITLE, andGITHUB_URL. - Edit
deploy.yml: change--base-url /your-repo-name/. - Create your
favicon.svg. - Push to
mainand verify the Actions tab shows a successful build. - Visit
https://username.github.io/your-repo-name/to confirm the site is live.
The reference projects phd-pagerank and hdr-p2p both follow this exact structure and can serve as working examples.
Working with an LLM
Both reference projects (phd-pagerank and hdr-p2p) were built with heavy LLM assistance. This chapter shares what worked, what did not, and how to structure the collaboration effectively.
What LLMs Excel At
LaTeX to Typst conversion
Give an LLM a LaTeX chapter and ask for clean Typst output. This works remarkably well for standard academic markup: sections, theorems, equations, figures, tables, citations, cross-references. A 30-page chapter typically converts in one pass with only minor fixes needed (usually around label formatting or package-specific commands).
Translation
Academic prose translates very well. The mathematical content stays unchanged (equations, labels, references are language-neutral), so the LLM only needs to handle the surrounding text. For a bilingual thesis, translating a French chapter to English (or vice versa) is one of the highest-value uses of an LLM.
Boilerplate generation
CSS stylesheets, build scripts, GitHub Actions workflows, README files — all the
infrastructure around the thesis content. These follow well-known patterns and an LLM can
produce working versions quickly. The entire style.css, nav.js, build.py, and
deploy.yml in the boilerplate were LLM-generated with iterative refinement.
Adapting existing patterns
“Make this file follow the same pattern as that file” is a prompt that works consistently. If you have one chapter converted and want to convert the next one in the same style, give the LLM both the source and the example output. It will replicate conventions (heading levels, label naming, theorem usage) accurately.
What Humans Must Do
Review all mathematical content
LLMs can subtly alter notation, drop indices, change quantifier scope, or rephrase a condition in a way that changes its meaning. Every equation, theorem statement, and proof step needs human verification. This is non-negotiable.
Make editorial decisions
Which chapters to include, how to structure the HTML navigation, what to translate versus keep in the original language, whether to split a long chapter into sub-pages — these are judgment calls that define the final product.
Verify cross-references
After conversion or translation, compile immediately. Broken labels (@some-label that no
longer exists) will show as errors. Cross-references between chapters are especially
fragile during restructuring.
Check specialized terminology
Domain-specific terms may not translate correctly. A “stable matching” in game theory is not the same as a “matching stable” in French (the adjective placement matters). Review translated terminology against the conventions of your field.
Final visual review
Both the PDF and the HTML output need a visual pass. Check that figures render correctly, theorem boxes have the right colors, math is not clipped, and the sidebar navigation makes sense.
Prompting Tips
Provide full context
Always give the LLM both the source file content and the target pattern or conventions. Do not rely on it guessing your project structure.
Be explicit about preservation
For conversion tasks, use prompts like: “Convert this LaTeX to Typst. Preserve ALL labels
(@xxx), references, math ($...$), footnotes, and heading structure. Do not summarize
or rephrase any content.”
For translation, be surgical
“Translate ONLY the French prose to English. Keep all Typst markup, labels, references, math environments, and code exactly as they are. Do not translate proper nouns or established technical terms.”
One file at a time
Process files individually for better quality. Bulk processing (multiple chapters in one prompt) leads to more errors, truncation, and inconsistencies. The overhead of separate prompts is small compared to the debugging cost.
Use parallel agents for independent work
If you have six chapters to translate, you can run six parallel agent sessions — one per chapter. They are independent tasks with no shared state. This is where LLMs save the most wall-clock time.
Iterative Workflow
The most effective process is a tight loop:
- Convert or translate a single file with the LLM.
- Compile immediately (
typst compile main.typor the HTML pipeline). - Fix any errors — broken references, missing imports, syntax issues.
- Visual check — open the PDF or HTML and scan for rendering problems.
- Commit the working state.
- Repeat for the next file.
This catches problems early. Do not batch too many LLM-generated changes before compiling — if you convert five chapters and then try to compile, debugging the accumulated errors is painful.
For the HTML pipeline specifically, a useful shortcut is:
uv run web/build.py --skip-pdf --skip-lang en --base-url /
This builds only the French HTML (skipping English and PDF), which is fast enough for iterative checking.
Cost-Effective Split
The most efficient workflow is:
- LLM for the 90% mechanical work: LaTeX-to-Typst conversion, prose translation, CSS/JS boilerplate, build script generation, README writing.
- Human for the 10% that requires judgment: mathematical review, editorial decisions, terminology verification, visual checks, project structure.
This matches how the two reference projects were actually built. The LLM produced the first draft of nearly every file; the human reviewed, corrected, and made structural decisions. The total effort was a fraction of doing everything manually, with the human time focused where it matters most.