Files
Vijay Janapa Reddi 38439425a6 Move all book sources under books/ and retire the duplicate tree
The repository carried the book twice. publishing/quarto/contents/vol{1..4}/
was what every build and every check read; books/vol{1..4}/ was a second
tracked copy that fed nothing and had drifted badly, holding 3 vol3 chapters
against 30 in the live tree. A `book` symlink pointed at publishing/, so
book/quarto/ and publishing/quarto/ were one directory under two names, next
to a books/ that was a third thing. Editorial work kept landing in the copy
nothing rendered.

books/ is now the Quarto project root and the only place book sources live:

  books/vol1..vol4          one directory per volume
  books/shared/             frontmatter, backmatter, _partials, assets, tex,
                            filters, scripts, audits, publish, calc
  books/config/             the per-volume quarto configs
  books/_extensions/        Quarto needs these at the project root
  books/index-vol{1..4}.qmd, references*.bib, _variables.yml, 404.qmd
  books/_build/             render output, gitignored and disposable

The toolchain stays in publishing/ and is now unambiguous, since it no longer
contains any of the book. The `book` symlink is gone and the CLI is reached as
./binder from the repository root; publishing/binder resolves through the
symlink so invoking it from either place works.

Config paths are relative to the project root, so a chapter entry goes from
contents/vol1/introduction/introduction.qmd to vol1/introduction/introduction.qmd,
and shared material picks up a shared/ prefix. 5,204 path references were
rewritten across 401 files, plus 127 segment-built paths in Python that string
matching could not see, plus 795 ./book/binder references.

Verified rather than assumed: a single-chapter HTML render produces output
equivalent to the same build on dev, 432,901 bytes against 433,158, the same
18 images, zero unresolved cross-references on both. Scoped checks return
identical findings on both trees.

One behaviour genuinely changes. While the sources sat under contents/, the
checks resolved their default scan root to the Quarto project directory rather
than the content directory, so a bare `./binder check tables` matched no files
and always reported success. Now that the project root and the content root
are the same directory the default works, and it immediately surfaces 31
pre-existing findings in vol3 and vol4 that were always present and never
reported. Those are content issues to fix separately, not regressions from
this move.

docs/REPO_LAYOUT.md describes the result. docs/BOOKS_MIRROR_RETIREMENT.md
records what the retired mirror held, which commits landed only there, and how
to recover any of it from history.
2026-09-10 13:12:19 +02:00

2.8 KiB

Post-Render Scripts

This directory contains scripts that run after Quarto builds to fix various issues.

resolve_cross_references.py

Problem It Solves

When using selective rendering to speed up builds (only building index + introduction instead of all 20+ chapters), Quarto cannot resolve cross-references to chapters that weren't built. This results in broken references appearing as ?@sec-chapter-name in the HTML output.

This particularly affects:

  • Glossary: Contains 800+ cross-references to all chapters (hence the original script name)
  • Introduction: References many other chapters for context
  • Any chapter that references other chapters

How It Works

  1. Runs automatically as a post-render hook after Quarto finishes building
  2. Scans ALL HTML files and the generated search.json in the _build/html/ directory
  3. Finds unresolved references that appear as <strong>?@sec-xxx</strong>
  4. Converts them to proper links: <strong><a href="../path/to/chapter.html#sec-xxx">Chapter Title</a></strong>

Configuration

The script is configured as a post-render hook in the Quarto configuration files:

# In config/_quarto-html.yml
project:
  post-render:
    - scripts/clean_svgs.py
    - scripts/resolve_cross_references.py  # Fixes cross-references

Maintenance

When adding new chapters to the book:

  1. Add the chapter's section ID to CHAPTER_MAPPING dictionary
  2. Add the chapter's display title to CHAPTER_TITLES dictionary
  3. Ensure the section ID matches what's in the .qmd file (e.g., {#sec-new-chapter})

Example:

CHAPTER_MAPPING = {
    # ... existing chapters ...
    "sec-new-chapter": "../core/new_chapter/new_chapter.html#sec-new-chapter",
}

CHAPTER_TITLES = {
    # ... existing chapters ...
    "sec-new-chapter": "New Chapter Title",
}

Manual Testing

# Test on all HTML files
python3 scripts/resolve_cross_references.py

# Test on specific file
python3 scripts/resolve_cross_references.py _build/html/contents/backmatter/glossary/glossary.html

Output

The script provides clear output showing what it fixed:

🔗 [Cross-Reference Fix] Scanning 3 HTML files and 1 search index file(s)...
✅ Fixed 850 cross-references in 2 files:
   📄 contents/backmatter/glossary/glossary.html: 810 refs
   📄 contents/core/introduction/introduction.html: 40 refs

clean_svgs.py

Cleans up SVG files generated during the build process.


Why These Scripts Exist

These post-render scripts enable fast iterative development by allowing selective chapter builds while maintaining a fully functional website with working cross-references. Without them, developers would need to build all 20+ chapters every time (taking minutes) just to test changes to a single chapter.