Skip to content

Extension integration

The Authoring reference documents Markdown syntax and zensical.toml options. This page explains the integration machinery used when changing an extension or embedding it in a renderer other than Zensical.

Share definitions across pages

Headings, references, citations, and glossary terms need build-wide state:

Extension Shared object Registered content
prodockit.headings and prodockit.refs IdRegistry Heading, figure, and table ids, labels, and destinations
prodockit.citations CitationRegistry Citation keys and authored reference text
prodockit.glossary GlossaryRegistry Term ids, display text, and definitions

Zensical creates a fresh Python-Markdown instance per page. Prodockit detects the Zensical page context, derives a source path, and shares the appropriate registry across the build. A pre-scan reads definitions from every navigation page before conversion so a page can refer forward to a definition rendered later.

Outside Zensical, the caller supplies the registry and source explicitly:

import markdown
from prodockit.headings import HeadingsExtension
from prodockit.refs import RefsExtension
from prodockit.util import IdRegistry

registry = IdRegistry()

for path, text in pages:
    html = markdown.markdown(
        text,
        extensions=[
            HeadingsExtension(registry=registry, source=path),
            RefsExtension(registry=registry, source=path),
        ],
    )

The equivalent constructors accept CitationRegistry and GlossaryRegistry. A manually shared registry raises DuplicateIdError for a collision between different sources. Zensical's best-effort automatic integration warns and keeps the first registration, so public guides require explicit ids for repeated headings such as “Overview”.

prodockit.headings.prescan() exposes the same continuous-numbering scan to template macros and other build tooling. It returns page-keyed starting counts and appendix letters while a Zensical build is active.

Delegate bibliography formatting

prodockit.bibliography does not implement CSL sorting, localisation, or disambiguation. It passes each distinct citation or reference-list request to pandoc --citeproc with the configured .bib and .csl files, then memoizes the formatted HTML for the rest of the build.

flowchart LR
    source[Markdown, BibTeX, and CSL] --> extension[prodockit.bibliography]
    extension --> citeproc[pandoc --citeproc]
    citeproc --> html[Formatted HTML]
    html --> website[Zensical website]
    html --> pdf[prodockit PDF]

Pandoc formats only the requested citation or list. Zensical still renders the surrounding page.

Generate an index after layout

Index markers exist before pagination, but their page numbers do not. The PDF pipeline therefore uses two PDF passes: the first locates every marked term; the second adds the sorted, deduplicated index with resolved page numbers. prodockit.pdf.index owns extraction and index construction.

Preserve website and PDF block behaviour

prodockit.steps follows the PyMdown Blocks API. Its start option emits both HTML <ol start="9"> and CSS counter-reset: list-item 8. Browsers honour the HTML attribute while WeasyPrint needs the CSS counter-reset. Removing either representation makes continued numbering disagree between outputs.

Tree and steps blocks expose stable class names for author styles. Their parser and output details can change only with matching website, PDF, and built-output tests.

Preserve table layout contracts

prodockit.tables runs after Python-Markdown's table extension and changes the generated HTML. Widths create a <colgroup> and mark the table with .prodockit-table-sized; compact tables use .prodockit-table-compact. Additional header rows move into <thead>, span placeholders are removed, and rotated headings use a transform because WeasyPrint does not reliably support writing-mode.

These transformations have consequences outside the extension. Zensical's theme styles many tables through .md-typeset table:not([class]), so adding a class means the project stylesheet must restore the normal border, padding, background, and dark-mode colours. Rotation also needs an explicit width because CSS transforms do not participate in layout. Keep website and PDF fixture coverage together when changing any of these contracts.

Preserve inline index content

The index inline processor runs early enough to retain nested Markdown such as emphasis, code, and links inside \\index{...}. The PDF stage later uses BeautifulSoup text extraction so the filing term is plain text even when the visible term contains nested HTML.

An attr_list placed after a linked index marker attaches to the outer index span, not the nested link. The author guide therefore recommends raw <a> markup when a linked term needs attributes. A parser change must cover this processor ordering and brace handling explicitly.

Preserve temporary attributes and public CSS hooks

The references, citations, and glossary extensions use temporary data-* attributes while resolving definitions. Strip those attributes from the final HTML, while retaining the documented public classes for resolved and unresolved output. The PDF then adds page-number text to resolved \autoref{} links with CSS target-counter().

The index is different: data-index-term and data-index-code remain in the rendered HTML because the PDF index stage consumes them after page layout. Treat those attributes as pipeline data rather than author styling hooks; the public styling hook is .index.