Skip to content

Publish Report

ContributorsMyles Henaghan

Contributors 🕹️ Myles Henaghan

When to use: Someone needs to understand a programme that currently only exists as a pile of markdown plans, effort JSON, ADRs and Confluence pages — and they are not going to read any of it.

Problem

A programme's truth is scattered: plans in one repo, architecture docs in another, effort tracking in JSON beside them, decisions in Confluence, and the current status in someone's head. The audience is mixed — an operations lead and a platform engineer need the same page to work for both.

The usual answers fail in specific ways. A slide deck goes stale the day it is made and cannot be rebuilt. A Confluence page cannot be read offline, cannot be sent to someone outside the tenancy, and drifts from the repo it describes. A browser "Save Page As" produces an HTML file plus a sibling _files folder, and breaks the moment either is moved without the other.

This skill produces one HTML file — no sibling folder, no CDN, no font server — built from source that lives next to the documents it summarises, and carrying the instructions for its own regeneration.

Instructions

Scope

For internal status and briefing pages: programme roadmaps, migration status, remediation progress, architecture briefs, decision summaries. Read by people inside the organisation, often forwarded, often opened offline.

Not for application UI — use the engagement's design system for that. Not for a document that is genuinely a document: if it has no figures, no status, and no structure worth navigating, write markdown instead.

The five steps

  1. Establish scope before reading anything. Ask what the page must answer, who reads it, and which repos and Confluence spaces hold the truth. Do not guess at source locations — a page built from the wrong document is worse than no page.
  2. Gather and reconcile the sources. Read every one in full. Follow the rules in source-handling.md — especially the ones about disagreeing documents and numbers with no provenance. If the caller supplied an authored overview, it is a source of a different kind: see below, and do not paraphrase it.
  3. Build the page from assets/, following page-architecture.md for the file layout and token discipline, and diagrams.md for figures.
  4. Compile and verify. scripts/build_standalone.py produces the single file; scripts/verify_page.mjs renders and checks it. See verification.md. Look at the screenshots.
  5. Embed the regeneration block so the next person does not need you. Default to the short brief that points back at this skill and the library — only restate the method inline when the rebuilder will not have either. regeneration-prompt.md.

Optional: an authored overview

The opening summary is the part of a status page a model is least qualified to write. It is a judgement call about what the numbers mean, and it carries the author's credibility rather than the repository's.

So the caller may hand over a markdown file instead, and its contents become the overview section directly:

/publish-report <page> ... --overview docs/status-pages/<slug>/overview.md

Anything the caller pastes into the conversation as "use this for the overview" counts the same way. assets/overview.md shows the shape: a heading, a governing paragraph, one supporting point per plan or workstream, and a byline.

When an authored overview is supplied, it is verbatim input, not a draft.

  • Do not rewrite, restructure, or improve it. Convert markdown to the page's HTML idiom, apply .mono to technical tokens, and stop.
  • Keep every hedge exactly as written. "I am unsure whether", "assumed blocked", "subjectively" are the author telling the reader how much weight to put on a claim. Firming them up is the single worst thing you can do here.
  • Do not add claims, however well evidenced elsewhere in the sources. If a plan states something more strongly than the author does, raise it with the author in your reply. Do not settle it on the page.
  • Do not turn a difference from the plans into a callout. The author is usually a co-author of those plans and is updating their own view. A callout framing it as a contradiction misreads who is speaking.
  • Sign it. An authored overview gets a .byline, so a reader knows whose judgement they are weighing. Everything below it is sourced; this part is not.
  • Fix typos, spelling of names, and obvious slips. Nothing else.

The rest of the page is generated as normal, and the fidelity rules in source-handling.md still govern it. Only the overview is exempt, and only because it has a named author.

Layout this skill produces

<repo>/docs/status-pages/<page-slug>/
  page.src.html          markup + theme toggle
  theme/
    report-tokens.css  placeholder palette + role tokens + dark extension
    report.css           component rules — no literal colour, no font name
  tools/                 copies of build_standalone.py and verify_page.mjs
  .cache/                downloaded webfonts; build cache, safe to delete
  README.md              how to build, verify, and regenerate
<repo>/docs/status-pages/dist/
  <Page Name>.html       the artefact people are sent

Source lives in the repo whose documents the page reports on, so the two move together. For a page spanning several repos, put it in the repo that owns the programme, and name the others explicitly in the Sources section.

Check the repo's .gitignore before choosing the folder name. A bare reports/ rule for generated analysis output is common, and it will silently swallow an authored page put under docs/reports/. Run git check-ignore -v <the new path> and pick a name the repo does not already claim.

Non-negotiables

  • One file out. The build fails if any external subresource or relative path survives. A page that needs a sibling folder or a font server is not done.
  • No literal colour, no literal font name, in a component rule. Everything resolves through a role token. This is what makes dark mode work and re-theming cheap; break it once and both stop being true.
  • Never invent a number, date, name, or status. A fact with no source stays out, or becomes an open question — never a stated fact.
  • Render before publishing. Diagram faults are invisible in the markup. The verifier exists because this exact page shipped with label overflows twice.
  • Theme follows the device, with an explicit override. Never hard-code one.
  • Content before provenance in the head. The title and description say what the page is about; how it was built goes below them. And name the output file well — an attached file previews on its filename alone.

Voice

When the page is for Wires Uncrossed, follow wue-messaging. Otherwise keep the page plain, direct, and operational: sentence case, lead with the figure. Two additions for this kind of page:

  • Explain a piece of jargon in plain language the first time it appears, then use the real technical term. The audience is mixed by definition.
  • Prose over bullets. Reserve lists for genuinely enumerable content. Any list over three items uses numbers or letters, so an item can be pointed at in a meeting ("see item 4").

References

Templates and scripts

File Use
assets/page.src.html Page skeleton with masthead, rail, diagram figure, sources, regen block, theme toggle
assets/report-tokens.css Placeholder palette, role tokens, dark extension — replace with the engagement palette when one exists
assets/report.css Component rules starter — masthead, cards, diagrams, pills, tables, callouts, byline
assets/overview.md Shape for an authored overview, when the caller supplies one rather than having it written
scripts/build_standalone.py Inliner. Standard library only. Produces the single file and proves it is self-contained
scripts/verify_page.mjs Render gate. Needs Playwright + Chromium; optional but strongly recommended
scripts/build_pdf.mjs Renders the built page to A4 PDF — the format that actually previews in Slack and Teams

Troubleshooting

Issue Cause Fix
Build reports "NOT self-contained" A url(), src or href could not be resolved Read the listed references; a typo'd local path is the usual cause
--no-network build fails Asset not in .cache/ Run once with network access to populate the cache, then commit or copy it
Built page renders with system fonts @font-face blocks dropped as non-Latin subsets Pass --keep-subsets latin,latin-ext,greek for the subsets you need
Output is huge (500 KB+) Discrete font weights each embed a file Request the variable weight axis instead: wght@0,400..700
Verifier exits 69 Playwright not found Set PLAYWRIGHT_MODULE, or skip it — the build gate still runs
A diagram label looks too big despite font-size="9" SVG presentation attributes lose to any CSS rule Use the .dia-xs / .dia-s / .dia-m classes
Dark mode partly broken A colour defined only inside a media query, or a literal hex in a component rule Move it to a role token defined in all three theme blocks
Theme toggle does nothing on file:// localStorage throws; the guard swallows it Expected. Auto still tracks the device; only the override does not persist