Skip to content

Cross-document references

A counter number only exists inside the conversion that allocated it. A second document citing FW-10 has no way to know what FW-10 is, and — worse — no way to notice when a renumbering turns it into FW-12. Hard-coded numbers in a sister document break silently, which is exactly the maintenance custom counters remove inside a document.

TeXSmith closes the loop the way DocBook's target database and Sphinx's objects.inv do: every conversion publishes a small JSON inventory next to its output, and a citing document declares the inventories it depends on.

# firmware-review.md — the document that publishes
---
title: Revue firmware
id: RHE-423
press:
  declare:
    counters:
      fw:
        name: Constat
        format: "FW-{n:02d}"
---
# hardware-review.md — the document that cites
---
title: Revue hardware
press:
  sources:
    crossrefs:
      fwrev: build/firmware-review.refs.json
---
L'écart est écrasé par l'étalement spectral (voir @fwrev:fw:pas-de-temps).

renders as

L'écart est écrasé par l'étalement spectral (voir RHE-423-FW-10 p. 14).

Identifying a document

id is a free label — a contract number, a report reference, whatever your organisation numbers documents with (document-id is accepted as an alias). When the target document declares one, a citation concatenates it with the item's label: RHE-423 + FW-10 → RHE-423-FW-10. That is what makes the reference unambiguous outside the document that defines it.

It is optional. Without it, a citation falls back to naming the document by its title — FW-10 (Revue firmware, p. 14) — and without a title either, to the bare label.

The inventory

Each conversion writes <document>.refs.json beside its .tex:

{
  "schema": 2,
  "document": {
    "id": "RHE-423",
    "title": "Revue firmware",
    "output": "firmware-review.pdf",
    "source": "../firmware-review.md",
    "hash": "8b66c613…"
  },
  "refs": {
    "fw:pas-de-temps": { "label": "FW-10", "page": 14, "kind": "counter", "prefix": "fw" }
  }
}

The shape is tmark's (tmark_registry::Inventory): tmark reads it, TeXSmith only writes it. Schema 1 named the map anchors, which tmark never read: an inventory written before 0.9.3 is reported as not valid (crossref-inventory-missing, "missing field refs") and resolves nothing. Rebuild the cited document.

It is delivered next to the artifact you asked for: in the directory for -o dir, beside the PDF for -o report.pdf. Its document.source is stored relative to its own location and is rewritten when it moves, so the staleness check keeps working.

It is written in two passes, because the two halves of a reference become known at different times. The keys and their labels are known while converting; the page numbers only exist once LaTeX has run, and are harvested from the .aux afterwards — the same source the xr package reads. A conversion without --build therefore publishes an inventory without pages, and citations simply omit the page.

Commit the inventory

It is a build artifact, but committing it makes renumbering diffable: git diff shows FW-04 → FW-10 before you publish. For a contractual document, that diff is the review gate that catches a renumbering before it reaches a reader.

Declaring and citing

press:
  sources:
    crossrefs:
      fwrev: build/firmware-review.refs.json # shorthand
      hwrev:
        inventory: ../hardware/build/hardware-review.refs.json

A top-level crossrefs: key is the deprecated spelling; tmark lint --fix moves it under press.sources.

Paths are relative to the citing document. The alias is then the first segment of the reference: @fwrev:fw:pas-de-temps.

Resolution is explicit: an alias never falls back to a local counter, and a local counter can never be shadowed by an external one. @fw:x is always this document's item, @fwrev:fw:x is always the other document's.

The target lives in a different PDF, so TeXSmith emits the formatted reference as plain text rather than a \hyperref that would resolve to nothing. Local references keep their live link.

Diagnostics

This is the part that earns the feature. All of these are diagnostics, shown by default and promotable to hard failures with --strict (or press.features.strict: true):

Situation Behaviour
the key is not published by the declared inventory warning, and the citation renders as [?fwrev:fw:disparu]
the inventory file does not exist yet warning, citations render as [?…]
the inventory's source changed since it was written warning: the numbers you are citing may already be wrong
the inventory's source no longer resolves at all warning: staleness can no longer be checked
the inventory uses a newer schema warning, inventory ignored

Warnings are attributed to the citing document, not to a TeXSmith source line:

review/hardware-review.md: warning ref-unresolved: Cross-reference '@fwrev:fw:disparu' is not published by …

An unresolved citation is deliberately visible in the output. A silently dropped reference is how a contractual document ships a hole.

Build order

hardware-review.pdf now depends on firmware-review.refs.json, which is a Makefile edge:

build/hardware-review.pdf: hardware-review.md build/firmware-review.refs.json

Two documents citing each other need two passes, exactly like LaTeX's own .aux: a missing inventory is a warning and not a failure, so the first build produces the inventories and the second resolves the citations.

Not implemented yet

Links from one PDF into another (xr-hyper does this with a PDF-viewer GoToR action), sections, figures and tables in the inventory (only counters are published yet), custom citation formats, and any form of automatic build ordering.