You drop a nice SVG diagram into a Distro story, hit save, and it is gone. No error, no warning — the published page just renders as if you never added it. The same thing happens to hand-styled layouts that depend on style blocks. It is one of the more maddening failure modes in the Distro editor, because nothing tells you anything went wrong.
The reason is sanitization. Distro cleans the HTML of every story before it renders, and svg, style, and script elements get quietly removed. Anything you paste in that relies on them evaporates. Worse, some workarounds fail later rather than immediately: a diagram encoded one way can survive the first save and then break the next time the story's owner opens the editor and re-saves.
distro-embed-diagram is a skill that teaches Claude the two embedding methods that actually hold up — through the sanitizer, through the backend's URL rewriting, and through repeated editor saves. Neither one needs file hosting, an S3 bucket, or any outbound network call, so Claude can do the whole job from a chat sandbox with just the Distro connector attached.
What you'll need
- The Distro Publisher connector (the MCP integration that lets Claude read and update stories on your publications).
- Code execution — Claude needs a sandbox to splice the HTML and, for the image path, to base64-encode a file. No internet access is required.
What it does
- Explains the two embed paths that survive Distro's rendering pipeline: a mermaid code block for diagrams where the structure is the point (flowcharts, sequence diagrams, timelines, org charts), and a base64 data-URI image for preserving finished artwork exactly as designed.
- Tells Claude which path to pick — mermaid when the diagram can be re-expressed as text, data-URI when you handed over a polished SVG whose look has to stay intact.
- Gives mermaid flowcharts the house color treatment: six colors that each stand for one kind of step, dark text on bright fills and a dark panel that reads the same in light and dark mode. The colors live in the diagram source, so they survive the sanitizer.
- Walks a safe edit workflow: fetch the story, save the exact existing HTML, confirm the insertion point appears exactly once, splice programmatically instead of retyping the body, then re-fetch and verify the block survived byte-for-byte.
- Guards against the classic accidents — sending partial HTML that wipes the rest of the story, or flipping a draft to published by including a status field in the update.
- Documents the traps in a quick-reference table, including why URL-encoded data URIs get mangled into dead links while base64 ones pass through clean.
- Notes that mermaid blocks stay readable to other AI agents pulling the story over the connector, since the diagram source doubles as plain text.
Install the skill
Ask Claude:
"Please fetch the distro-embed-diagram skill from Distro Skills and give me a
.skillfile I can install."
Claude will pull this story, extract the SKILL.md from the fenced block below, and package it as distro-embed-diagram.skill. The file will appear in your chat with a Save skill button — click it to install directly. That registers the skill with your Claude app so it shows up in your skills list and triggers on matching requests.
Requires the Distro Reader or Distro Publisher MCP connected so Claude can pull the story.
Claude Code users: you have a direct path — ask Claude to drop the unzipped folder into ~/.claude/skills/distro-embed-diagram/ and restart the session. No packaging step needed.
No Distro MCP connected? Copy the SKILL.md contents from the fenced block below into a file called SKILL.md inside a folder called distro-embed-diagram, zip it (folder as zip root), and upload through Settings → Capabilities → Skills → Upload skill.
The skill source
This is the canonical SKILL.md:
---
name: distro-embed-diagram
description: Use when the user wants an SVG diagram, flowchart, chart or infographic embedded in the body of a Distro story or draft using only the Distro MCP connector — including when inline svg or style markup disappears after saving. Two hosting-free paths, mermaid code block or base64 data-URI image. Requires the Distro connector plus code execution; no outbound network or file hosting needed.
---
# Embed a Diagram in a Distro Story (MCP-only)
Note: this file spells HTML elements without angle brackets (the skill uploader rejects XML tags). When editing story content, write them as normal HTML tags.
## Overview
Distro sanitizes story HTML at render: literal `svg`, `style`, and `script` elements are stripped **silently** — an inlined SVG simply vanishes from the published page. Never inline them. Two embed paths survive every layer (sanitizer, backend URL rewriting, the owner's later editor re-saves), and neither needs S3, file hosting, or outbound network:
- **Path A — mermaid block.** For diagrams whose structure is the point: flowcharts, sequence, state, timelines, org charts. Renders as a live diagram on the story page and degrades to readable source for agents reading over MCP.
- **Path B — base64 data-URI image.** For preserving exact artwork: an existing .svg file, brand-styled graphics, custom layouts mermaid can't express.
Prefer Path A when the diagram can be re-expressed as mermaid; use Path B when the user supplied a finished SVG whose look must be preserved.
## Shared workflow (both paths)
1. **Fetch the story**: `distro_content_get(contentId)` → save the exact `content` HTML to a sandbox file. Confirm the insertion point matches **exactly once**; zero or multiple matches → stop and ask the user.
2. **Splice with a script, not by retyping.** Every byte of the body outside the insertion stays identical.
3. **Update**: `distro_content_update(contentId, content: FULL_MODIFIED_HTML, contentFormat: "html")`. Omit `status`/`title`/`preview` so a draft stays a draft. Never send partial HTML — `content` replaces the whole body.
4. **Verify**: re-fetch and confirm your block survived byte-for-byte (data URIs and mermaid blocks are returned verbatim — no re-signing).
## Path A — mermaid block
Splice a pre element wrapping a code element with class "language-mermaid" (both rendered as real HTML tags), whose text content is the mermaid source, e.g.:
graph LR
A[Agent] -->|request| B{Paywall}
B -->|paid| C[Story]
The story page live-renders it as a diagram; the web editor treats the block as one atomic mermaid node, so the owner's re-saves won't flatten it. Keep the source valid mermaid — it doubles as the plain-text form agents see.
### Color treatment (Path A)
Give every mermaid flowchart the house color treatment unless the user asks for plain. It matches the JournoBot explainer (Distro Newslode story 903) and lives entirely in the mermaid source, so it survives the sanitizer with no customCss.
1. **Palette by role, not by taste.** Put these classDef lines straight after the diagram header, which itself follows the theme line from step 7. Each color stands for one kind of node, so readers learn it once:
classDef neutral fill:#94a3b8,stroke:#475569,color:#0b1220,stroke-width:2px
classDef decision fill:#f59e0b,stroke:#b45309,color:#0b1220,stroke-width:2px
classDef message fill:#a855f7,stroke:#6b21a8,color:#0b1220,stroke-width:2px
classDef step fill:#0ea5e9,stroke:#0369a1,color:#0b1220,stroke-width:2px
classDef outcome fill:#22c55e,stroke:#15803d,color:#0b1220,stroke-width:3px
classDef failure fill:#ef4444,stroke:#991b1b,color:#0b1220,stroke-width:2px
Rename the classes to fit the diagram, but keep the meanings: amber = a decision or the person making it; purple = messages, emails, invites, hand-offs; sky = steps the user walks through; green with the 3px stroke = the outcome the diagram is about; red = failure or rejection; slate = neutral nodes and dead ends. Use at most six classes.
2. **Dark text on every fill** (color:#0b1220) and a darker stroke of the same hue. Never white text on the bright fills.
3. **Assign classes in one block at the end** (one line per class, e.g. `class W1,W2,W3 step`) rather than `:::class` on each node. Regrouping becomes a one-line edit.
4. **Wrap the block in a dark panel** so it reads the same in light and dark mode: a div with the attribute data-distro-rich-block="" containing a div with inline style `color:#e2e8f0; background:#0b1220; padding:32px 28px; border-radius:14px; max-width:1040px; margin:0 auto`, and the pre/code block inside it. Any caption or source line inside the panel takes `color:#94a3b8`.
5. **Escape the source.** Inside the code element write line breaks in labels as `<br/>` and arrows as `-->`, exactly as the editor stores them. Quote every node and edge label. A literal ampersand in a label is written `&`: the app reads the source with textContent, which decodes entities once.
6. **Never invent a legend.** If the user wants one, ask for the wording or use the role names they approved.
7. **Force Mermaid's dark theme.** Make the first line of the source `%%{init: {"theme": "dark"}}%%`, above the diagram header. The app draws Mermaid in its default theme when the reader is in light mode, and that theme's dark gray lines and arrowheads nearly vanish on the dark panel. The init line keeps the diagram matched to the panel in both modes. It works for every diagram type, sequence diagrams included.
## Path B — data-URI image
1. Base64-encode the .svg file in the sandbox (pure local encoding — no network).
2. Splice an img tag of exactly this shape (rendered as a real HTML tag):
`img src="data:image/svg+xml;base64,PAYLOAD" alt="describe what the diagram shows" style="width:100%;height:auto;"`
Plain img only — no figure/figcaption wrappers, no extra attributes.
**Base64 is mandatory.** A URL-encoded SVG payload can contain dots, which the backend's image re-signer misparses as a filename and replaces with a broken URL. Base64 payloads pass through untouched (verified end-to-end).
## Gotchas
| Trap | Reality |
|------|---------|
| Inline `svg` / `style` / `script` in the body | Stripped silently at render — the diagram just vanishes. Use Path A or B. |
| Recreating a diagram as styled divs | Inline `style=""` attributes survive, but `position:fixed/absolute/sticky` and `z-index` are stripped from them — connector/overlay layouts break. Prefer Path A or B. |
| URL-encoded (non-base64) data URI | May be mangled into a dead presigned URL by the backend. Base64 only. |
| Wanting a reusable hosted URL for the file | That needs the S3 presigned-upload path, which needs outbound network — unavailable here. Hand off to Claude Code or the web app's uploader. |
| Draft flips to published | Omit `status` on `distro_content_update`. |
| Huge base64 payloads | Story bodies are Postgres TEXT — size is not a constraint. |
When to use
- You want a flowchart, sequence diagram, timeline, or org chart inside a Distro story or draft, optionally in the house colors.
- You have a finished SVG graphic and need it to appear in the story body exactly as designed.
- A diagram you added earlier vanished after saving, or turned into a broken image link.
- You are working through the Distro connector alone, with no ability to upload files to a host.
When not to use
- You need the image as a reusable hosted URL — for a featured image, a social card, or reuse across stories. That requires the S3 presigned-upload path and outbound network access, so hand it to Claude Code or the web app's uploader.
- You are creating the artwork itself rather than embedding it; a design skill covers that, and this one takes over at the embed step.
- You are publishing a plain text story with no visual element.
Feedback
This is v1.1. Edge cases or suggestions → reply or ping Bradley Keoun.