report-render
A governed artifact in the stable bundle. Replacing this file on
your machine makes your next attestation come back Drifted, naming this path.
| Description | Render a STANDALONE visual artifact to the visual-bar standard — interactive HTML (Plotly charts + vis-network graphs) PLUS a branded PNG companion, phone-readable by default. Use when the deliverable is chart/graph/visual-shaped: 'visualize X', 'show me X as a chart/heatmap/graph', 'plot this data', 'dependency graph of X', 'render/build the HTML+PNG', 'the visual-bar companion' — explicit HTML+PNG wording NOT required. PNG is NOT optional, Mermaid is REJECTED, phone-first layout is the default. NOT for PM planning/roadmaps/boards, NOT for web-app dashboard pages (that is app work), NOT for prose reports (docx), decks (pptx), or spreadsheets (xlsx). |
|---|---|
| Arguments | none |
| Tools it may use | [Read, Write, Edit, Bash] |
| Installs at | skills/report-render/SKILL.md |
| Mode | 0644 — never executable |
| Size | 10595 bytes |
| SHA-256 | aab06185407efe01f669eb51e0ec84b30204c4e02f52fbfb471bc01b31919fb7fetch the raw bytes |
The complete file, verbatim
This is the entire SKILL.md, including its YAML frontmatter:
byte-identical to what installs, not rendered and not reformatted. Hash exactly what is below
and you get the digest above. The table is only a reading aid; this block is the artifact.
---
name: report-render
description: Render a STANDALONE visual artifact to the visual-bar standard — interactive HTML (Plotly charts + vis-network graphs) PLUS a branded PNG companion, phone-readable by default. Use when the deliverable is chart/graph/visual-shaped: 'visualize X', 'show me X as a chart/heatmap/graph', 'plot this data', 'dependency graph of X', 'render/build the HTML+PNG', 'the visual-bar companion' — explicit HTML+PNG wording NOT required. PNG is NOT optional, Mermaid is REJECTED, phone-first layout is the default. NOT for PM planning/roadmaps/boards, NOT for web-app dashboard pages (that is app work), NOT for prose reports (docx), decks (pptx), or spreadsheets (xlsx).
allowed-tools: [Read, Write, Edit, Bash]
---
# report-render — the visual-bar engine
Turns the visual-bar standard from prose-rebuilt-by-hand-every-time into one repeatable
render.
**Why it exists (measured):** an audit of our own session corpus found **73** separate
report-shaped deliverables, each one re-deriving the palette, re-inventing the dual-output
step, and each one slightly different from the last. The variance was the cost, not the
effort. **This skill is the engine that makes the visual-quality gate cheap for every other
skill** — once it exists, no other workflow has to relitigate what "a good report" means.
---
## Phone-first is the DEFAULT layout
Assume your reader opens the deliverable on a phone. Every artifact, unless the requester
names a desktop-only context:
- single-column flow
- base font >= 15px
- tap targets >= 40px
- charts full-width with `responsive: true`
- tables that reflow, or scroll inside their own container (never force the page to scroll
sideways)
- no hover-only affordances — every tooltip needs a tap equivalent
- the PNG companion legible at 400px wide
**If the desktop layout and the phone layout fight, the phone wins.** We learned this the
expensive way: this preference was re-stated roughly fifteen times across the corpus before
anyone wrote it down as a default. Every restatement was a deliverable that had to be redone.
---
## The standard — non-negotiable
1. **Two artifacts, always.** An **interactive HTML** (Plotly for charts; vis-network for
entity / dependency graphs) **AND** a **static branded PNG** companion. The PNG is **not
optional** — a deliverable without it fails the bar. Rationale: the HTML is for
exploration, the PNG is for the places a deliverable actually gets consumed — chat, a
message thread, a slide, a phone lock screen. An HTML-only artifact is invisible in half
its destinations.
2. **Mermaid is rejected.** Network and relationship views use vis-network. Mermaid's layout
is not controllable enough to hit the bar, and its output is not brandable.
3. **One branded palette** on both artifacts (see below). Same colors, same semantics,
every time.
4. **Single-file HTML with CDN dependencies** — data and layout inline, libraries from CDN.
Opens by double-click **when online**. Be honest that this is **not** truly
self-contained: no network, no charts. If the artifact must work offline, vendor the JS
locally; otherwise **state the CDN dependency in the handoff** rather than letting the
recipient discover it on a plane.
---
## Palette
This is our dark palette. If you are adopting this skill at another organization, swap these
values for your own brand — the *rule that matters* is that there is exactly one palette and
every artifact uses it.
```
BG #0f1420 PANEL #1a2233 INK #eceef5 MUTE #8e8e93
CYAN #00e5ff BLUE #448aff GREEN #00e676 AMBER #ffd740
ORANGE #ff6e40 RED #ff1744
CYAN_SCALE [0]#10151f [.15]#143049 [.4]#1f5fae [.65]#2f8fd6 [.85]#00c2e0 [1]#aef2ff
AMBER_SCALE [0]#1a1410 [.2]#3d2a12 [.5]#9c5410 [.75]#ff8c00 [.9]#ffb300 [1]#ffe27a
```
Two sequential scales, not one: CYAN for "more is neutral or good" (volume, coverage,
throughput), AMBER for "more is heat" (cost, risk, load). Picking the scale is a semantic
decision, not a decorative one.
---
## The reference pattern — mirror it, don't reinvent
Keep **one** working generator in your repo and mirror its structure for every new report.
Ours is a heatmap generator; the shape generalizes. Write a small generator that emits both
artifacts from the same data structure in one run:
```python
# report_gen.py — the shape every report generator should have
import plotly.graph_objects as go
import matplotlib
matplotlib.use("Agg") # headless: no display server needed
import matplotlib.pyplot as plt
PALETTE = {...} # the block above, one source of truth
def load() -> dict:
"""Read the data. Keep this the only I/O-shaped function."""
def normalize(data: dict) -> dict:
"""Per-row normalization so one hot row doesn't flatten every other row."""
def render_html(data, out_path: str) -> None:
fig = go.Figure(...)
fig.update_layout(
paper_bgcolor=PALETTE["BG"], plot_bgcolor=PALETTE["PANEL"],
font=dict(color=PALETTE["INK"], size=15),
margin=dict(l=8, r=8, t=48, b=8), # phone-first: minimal gutters
)
fig.write_html(out_path, include_plotlyjs="cdn",
config={"responsive": True, "displayModeBar": False})
def render_png(data, out_path: str) -> None:
"""A separate render, NOT a screenshot of the HTML."""
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=160)
fig.patch.set_facecolor(PALETTE["BG"])
...
fig.savefig(out_path, facecolor=PALETTE["BG"], bbox_inches="tight")
```
**Per-row normalization matters.** A single dominant row will otherwise wash every other row
out into the same near-black cell, and the chart ends up saying nothing.
**PNG export: prefer matplotlib.** Plotly + kaleido also works if installed, but kaleido
pulls a headless-Chrome dependency that fails silently in containers and on locked-down
build hosts. matplotlib has no such dependency. Choosing matplotlib removed an entire class
of "the file exists but is 0 bytes" failures for us.
---
## Build flow
1. **Lead with the ONE headline.** Before any chart: what is the single insight this report
exists to surface? Put it at the top, big. The visuals support the headline; they are not
a substitute for having one. A report that opens with a chart and no claim makes the
reader do the analysis you were supposed to do.
2. **Render the two artifacts.** Write the generator, run it, produce `<name>.html` +
`<name>.png`. If your environment has an artifact-publishing hook or a standard place
these are meant to land, register the output with it as the last step.
3. **Low cognitive load.** One screen = one idea. Don't cram; split into sections.
4. **Surface the result, don't bury it.** Print the paths prominently, or attach the PNG
directly. The visual is the payoff — burying it three paragraphs into a wall of prose
defeats the entire exercise.
---
## Honest output
Report the **exact paths** of both artifacts.
If the PNG export **fails** (missing export backend, plotting error, empty frame), say
**"PNG: FAILED — <reason>"** and do **not** claim the bar is met. HTML-only is an incomplete
deliverable and gets reported as such — never silently.
**PNG visual QA:** after export, actually **open and inspect** the PNG. Confirm it rendered —
not blank, not clipped, not garbled, axis labels not overlapping into mush. Confirming that
*a file exists* is not confirming that *an image is correct*; those are different claims, and
only one of them is what the reader cares about. If visual QA was not run, report
**"PNG QA: NOT-RUN"**. A check that did not run is NOT-RUN — never a silent skip, and never
downgraded to a pass.
---
## NOT this skill
- Word doc -> a document-authoring skill · slides -> a deck skill · spreadsheet -> a
spreadsheet skill
- Generative / algorithmic art -> an art skill
- Just *reading* a chart someone else made -> native file read
- Roadmaps, boards, and PM planning artifacts -> your PM planning skill
- Pages inside a live web app -> that is application work, not artifact rendering
---
## Definition-of-Done self-check (honest)
This is the shape of the DoD we hold skills to. It is reported as-is, including what did not
run — the honesty is the point, and a self-check that only ever reports MET is not a check.
1. **Trigger vocabulary** — **MET** (narrowed after review). Scoped vocab (render HTML+PNG,
chart / graph / heatmap, plot data, visual-bar companion) plus explicit negatives (PM
planning, app/dashboard build, docx/pptx/xlsx, art, review-only).
2. **allowed-tools scoped** — **MET.** `Read` (data + the reference generator), `Write`/`Edit`
(generator + artifacts), `Bash` (run the render). No MCP access, no blanket extras.
3. **Cites its reason** — **MET.** 73 report-shaped deliverables in the corpus; the standing
visual-bar rule in team memory; it is the enabler for the visual-quality gate in every
other skill.
4. **Grounded facts** — **MET** (verified). The palette and the dual-output
Plotly-HTML + matplotlib-PNG approach are lifted from a generator that actually runs, not
invented for the write-up.
5. **Honest reporting** — **MET.** PNG-FAILED is reported explicitly; HTML-only never passes
the bar silently; PNG QA has an explicit NOT-RUN state.
6. **Trigger-fire validation** — **NOT-RUN** (needs live routing in a fresh session). Prompts
defined: (1) "visualize this cost breakdown" -> should fire; (2) "render the sprint board
as a report" -> should fire; (3) "make me a dependency graph of these services" -> should
fire, vis-network path. Negatives that must NOT fire: "make a slide deck of this",
"put this in a spreadsheet". **Run before shipping.**
7. **Visual bar** — **DESIGNED; NOT-RUN.** It *is* the visual bar by design, but no sample
artifact has been generated and QA'd. The fire-test must produce one small sample
HTML+PNG and visually QA the PNG before this flips to MET.
8. **Reader fit** — **MET.** Designed for a time-poor reader who is context-switching and
usually on a phone: leads with the one headline insight, one-screen-one-idea, honest on
failure, and the visual itself is the reward for reading. Litmus test: a well-branded
report should surface the next step at a glance — it should make the reader lean in, not
close the laptop.
**Open item:** trigger-fire validation. Run the three fire prompts above, generate one small
sample HTML+PNG, and eyeball the PNG before you rely on this skill in your own project.
