--- name: md-document description: > Convert authored markdown into a polished self-contained HTML document with a table of contents, numbered figures and tables, cross-references, footnotes, and print-ready CSS. Use when publishing a report, whitepaper, or memo. license: MIT + Commons Clause metadata: version: 1.0.0 author: borghei category: markdown-html domain: document-publishing updated: 2026-07-21 tags: [markdown, html, publishing, pdf, cross-references, typography] --- # Markdown Document Publishing Turn an authored markdown file into a single HTML document you can email, host, or print — semantic structure, an automatic table of contents, figures and tables numbered and referenceable by number, footnotes, and a print stylesheet that survives contact with a PDF exporter. One file out, no runtime dependencies, no external assets. ## When to use this skill - **Publishing a report or whitepaper** that must arrive as one file, not a folder - **Producing a PDF** from markdown without a LaTeX or Pandoc toolchain - **Numbering figures and tables** so prose can reference them instead of saying "below" - **Converting untrusted or contributed markdown** where injection safety matters - **Standardizing a document series** so every issue looks like the same publication - **Catching broken cross-references** before a document reaches readers ## Inputs the skill expects - A markdown source file, optionally with YAML frontmatter - Figure images as relative paths (or data URIs, for true single-file output) - The intended output: screen, print, or both - Document length and whether a table of contents is warranted - Page geometry, if printing: size, margins, single- or double-sided - A stylesheet, if not using the bundled theme ## Clarify First Before converting, confirm these inputs. If any is unknown or vague, ASK — do not assume: - [ ] **Screen, print, or both** — why it changes the output: print needs a page profile, forced light colors, and break control; skipping it produces a document that looks right on screen and breaks on paper - [ ] **Whether images must be embedded** — why it changes the output: relative image paths mean the HTML file is not actually self-contained, which defeats the point if it will be emailed - [ ] **Document length and TOC expectation** — why it changes the output: a TOC on a two-page memo is noise; depth 3 on a long report produces a TOC longer than some sections - [ ] **Whether the markdown is trusted** — why it changes the output: it does not change escaping (always on), but it determines whether a review gate should run first Stop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact. ## Workflows ### Workflow 1 — Convert a document to self-contained HTML 1. Audit labels and references first — the converter's gate reports broken references, but the auditor explains what to do about each one. 2. Convert. The bundled theme is inlined automatically; pass `--css` to override. 3. Check the gate. A non-zero exit means the rendered document contains visible `[?fig:name]` markers where numbers should be. ```bash python3 markdown-html/md-document/scripts/crossref_auditor.py \ --input markdown-html/md-document/assets/sample_document.md --format text python3 markdown-html/md-document/scripts/md_to_html.py \ --input markdown-html/md-document/assets/sample_document.md \ --out build/report.html --toc-depth 2 --format text ``` ### Workflow 2 — Produce a print-ready PDF 1. Generate the print block from a page profile and append it to the theme, so the exported file carries its own print rules. 2. Convert with the extended stylesheet. 3. Open in a browser, print to PDF with margins set to **Default** — a browser margin setting overrides `@page` and will clip content. ```bash python3 markdown-html/md-document/scripts/print_profile.py \ --input markdown-html/md-document/assets/sample_print_profile.json \ --out build/print.css --format text cat markdown-html/md-document/assets/document_theme.css build/print.css > build/full.css python3 markdown-html/md-document/scripts/md_to_html.py \ --input markdown-html/md-document/assets/sample_document.md \ --css build/full.css --out build/report.html ``` ### Workflow 3 — Gate a document series in CI 1. Run the auditor at `warning` severity so unlabelled figures block, not just broken references. 2. Run the conversion gate; it fails on unresolved references and undefined footnotes — both render as visible defects. 3. Emit JSON for both so a CI job can annotate the diff. ```bash python3 markdown-html/md-document/scripts/crossref_auditor.py \ --input markdown-html/md-document/assets/sample_document.md --max-severity warning --format json python3 markdown-html/md-document/scripts/md_to_html.py \ --input markdown-html/md-document/assets/sample_document.md --out build/report.html --format json ``` ## Decision frameworks ### Table-of-contents depth | Document length | TOC | `--toc-depth` | |-----------------|-----|---------------| | Under 3 pages | none — omit `[TOC]` | n/a | | 3-10 pages | yes | 1 (`##` only) | | 10-30 pages | yes | 2 (default) [PROVEN] | | Over 30 pages | yes | 2, plus per-section navigation | If the TOC exceeds one screen, reduce the depth. A contents list longer than the first section is a navigation failure, not thoroughness. ### Reference style | Situation | Write | Not | |-----------|-------|-----| | Pointing at a figure | `[@fig:access]` | "the chart below" | | Pointing at a table | `[@tbl:policies]` | "see the table above" | | Pointing at a section | `[@sec:context]` | "as discussed earlier" | | A caveat that breaks the sentence | a footnote | a parenthetical | | Evidence the argument depends on | body text | a footnote | **[PROVEN] Never use positional language in a document that may be paginated.** "Below" breaks when the table lands on the next page, breaks silently when a section is reordered, and means nothing to a reader navigating by heading. ### Alt text versus caption | | Alt text | Caption | |---|---------|---------| | Audience | Non-sighted readers | Everyone | | Length | 15-125 characters | One or two sentences | | Says | What the image depicts | What to conclude, plus the number | | Fails as | "chart", "figure 3", "" | "See above" | The auditor flags placeholder alt text (`chart`, `image`, `screenshot`, empty) at **error** severity and alt text under 15 or over 125 characters at **warning**. ### Print geometry | Decision | Default | Change when | |----------|---------|-------------| | Page size | A4 [RECOMMENDED] | Audience is exclusively North American → Letter | | Side margins | 25-30mm | Never below 20mm — the measure exceeds 90 characters | | Body size | 11pt | 12pt for older audiences or dense reference material | | Mirrored margins | off | The document will be bound double-sided | | `break-inside: avoid` | figures, tables, code | Never on an element taller than one page | At A4 with 20mm margins the text column is ~92 characters — well outside the 55-85 comfort band. Widening the margins is the fix; `max-width: none` on `main` is what causes the problem. ## Anti-Patterns ### The "see the table below" reference **Mistake:** Writing positional prose — "the chart below", "as shown above" — instead of a numbered cross-reference. **Why it happens:** It reads naturally while drafting, when the author can see the whole document at once and the table genuinely is below. **Instead:** Write `[@tbl:policies]`. Pagination moves content, reordering breaks positional claims silently, and a reader navigating by heading has no "below". The auditor cannot detect a broken "below"; it fails the build on a broken `[@tbl:policies]`. ### Allowing raw HTML through the converter **Mistake:** Adding an escape hatch so authors can drop `