Plank help · updated 2026-08-09

How Plank delivers finished documents

Reports, analyses, and briefs are delivered as clean, self-contained HTML documents that match your light/dark theme automatically — and how to change that.

Agents: fetch the raw markdown of this page at /en/help/deliverables.md

How Plank delivers finished documents

When Plank produces something for you to read — a report, an analysis, a brief, a summary — it delivers a clean, self-contained HTML document by default, not raw markdown. One file, styled in Plank's house style, that looks finished the moment you open it and still looks right if you download or forward it.

This page is written for both you and the assistant. The assistant fetches it at https://plank.md/help/deliverables.md before writing a deliverable, so every document comes out consistent.

What counts as a deliverable

Delivered as styled HTML: documents a person reads — reports, analyses, briefs, summaries, write-ups.

Not changed by this:

  • Working notes the assistant keeps to stay organized (log.md, index.md, the knowledge/ folder) stay as markdown — they're the assistant's filing system, not documents you sit down to read.
  • Spreadsheets stay .xlsx, and you can still ask for a .pdf or Word file. HTML is the default for written documents, not a replacement for those formats. Slide decks have their own house style and their own two formats — an editable .pptx, or HTML printed to PDF when the deck has to look art-directed rather than be re-cut. The genre decides; that page explains what each one gives up.

Charts and dashboards have their own page too — Dashboards & charts covers the chart chooser, the validated palette and the self-contained components.

When the finding is a relationship rather than a number — an order, a handoff, what contains what, where the time goes — a drawn figure hands it over intact where a paragraph makes the reader rebuild it. Diagrams is the page for that: when a diagram beats a paragraph or a table and when it plainly doesn't, the inline-SVG block to paste into the sheet, and the contract it has to meet. Fetch it before drawing anything into a deliverable — a diagram copied from a general-purpose diagram tool bakes its colours in, so it is invisible in the other theme and unnamed to a screen reader, and nothing at render time will tell you.

The quality bar is a skill, not a rule on this page. Two first-party skills — Deliverable Writing (titles that state the finding, numbers with a baseline, the closing decision) and Deliverable Design (focal point, spacing, legible charts) — carry the standard across decks, documents and dashboards. If either is installed in this workspace, invoke it before you deliver. See Skills.

The house style

Every deliverable is a single self-contained .html file — all styling is baked in and the theme follows light/dark automatically, so it renders correctly on its own with nothing else attached.

  • Type set in Inter, with JetBrains Mono for figures in tables.
  • Warm-ivory page and a white "sheet" in light mode; deep obsidian and slate in dark mode.
  • Plank blue accent, generously but calmly spaced, squared-off corners — a document, not an app.
  • Matches your light/dark theme automatically and switches live when you change it — in Plank it follows the app's theme, on its own it follows your system setting. No toggle to manage.
  • Responsive: the sheet collapses to full-width on a phone.

Canonical template

Start from this exact template and fill the <article class="sheet"> with the deliverable's content, using only the pieces it needs (eyebrow, title, lead, meta row, stat grid, sections, lists, callout, table, footer). Do not restyle it ad hoc — consistency is the point.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{TITLE}}</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap"
      rel="stylesheet"
    />
    <style>
      :root {
        --ivory: #fafaf7;
        --surface: #ffffff;
        --stone: #f0ede8;
        --mist: #e5e2dc;
        --ink: #0a0a0f;
        --graphite: #3a3a42;
        --muted: #72706b;
        --faint: #aaa69e;
        --blue: #4f6df5;
        --blue-soft: #eef1fe;
        --blue-on: #243fbf;
        --green: #2f9461;
        --green-soft: #edf8f2;
        --shadow: 0 24px 80px rgba(10, 10, 15, 0.08);
      }
      :root[data-theme="dark"] {
        --ivory: #0a0a0f;
        --surface: #16161d;
        --stone: #20202a;
        --mist: #2a2a35;
        --ink: #fafaf7;
        --graphite: #d6d3cc;
        --muted: #918e87;
        --faint: #6a685f;
        --blue: #7d93f8;
        --blue-soft: #1c2030;
        --blue-on: #aab8fb;
        --green: #4cc189;
        --green-soft: #14241c;
        --shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
      }
      * { box-sizing: border-box; }
      body {
        margin: 0;
        background: var(--ivory);
        color: var(--ink);
        font-family: Inter, system-ui, sans-serif;
        font-size: 16px;
        line-height: 1.55;
        -webkit-font-smoothing: antialiased;
        transition: background 0.25s ease, color 0.25s ease;
      }
      .sheet {
        max-width: 760px;
        margin: 40px auto;
        background: var(--surface);
        border: 1px solid var(--mist);
        border-radius: 10px;
        box-shadow: var(--shadow);
        padding: clamp(24px, 5vw, 52px);
      }
      .eyebrow {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        border-radius: 6px;
        background: var(--blue-soft);
        color: var(--blue-on);
        padding: 6px 12px;
        font-size: 12px;
        font-weight: 700;
        letter-spacing: 0.02em;
      }
      h1 {
        margin: 18px 0 0;
        font-size: clamp(30px, 5vw, 44px);
        line-height: 1.05;
        letter-spacing: -0.04em;
        font-weight: 700;
      }
      .lead {
        margin: 14px 0 0;
        color: var(--graphite);
        font-size: 18px;
        line-height: 1.5;
      }
      .meta {
        display: flex;
        flex-wrap: wrap;
        gap: 8px 22px;
        margin-top: 20px;
        padding-top: 18px;
        border-top: 1px solid var(--mist);
        color: var(--muted);
        font-size: 14px;
      }
      .meta b { color: var(--graphite); font-weight: 600; }
      h2 {
        margin: 38px 0 0;
        font-size: 23px;
        letter-spacing: -0.02em;
        font-weight: 600;
      }
      h2 + p, h2 + ul { margin-top: 12px; }
      p { margin: 14px 0 0; color: var(--graphite); }
      ul { margin: 16px 0 0; padding-left: 0; list-style: none; }
      li {
        position: relative;
        padding-left: 26px;
        margin-top: 10px;
        color: var(--graphite);
      }
      li::before {
        content: "";
        position: absolute;
        left: 6px;
        top: 11px;
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: var(--blue);
      }
      .callout {
        margin-top: 20px;
        border: 1px solid var(--mist);
        border-left: 3px solid var(--blue);
        border-radius: 8px;
        background: var(--ivory);
        padding: 16px 18px;
      }
      .callout strong { color: var(--ink); }
      .stats {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 14px;
        margin-top: 20px;
      }
      .stat {
        border: 1px solid var(--mist);
        border-radius: 8px;
        background: var(--ivory);
        padding: 16px;
      }
      .stat .n {
        font-size: 32px;
        font-weight: 700;
        letter-spacing: -0.04em;
        color: var(--blue);
      }
      .stat .k { margin-top: 4px; color: var(--muted); font-size: 13px; }
      table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 16px;
        font-size: 15px;
      }
      th {
        text-align: left;
        padding: 11px 14px;
        color: var(--muted);
        font-size: 12px;
        font-weight: 700;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        border-bottom: 1px solid var(--mist);
      }
      td {
        padding: 10px 14px;
        border-bottom: 1px solid var(--stone);
        color: var(--graphite);
      }
      td.num { font-family: "JetBrains Mono", monospace; text-align: right; }
      .tag {
        display: inline-flex;
        padding: 3px 9px;
        border-radius: 6px;
        font-size: 12px;
        font-weight: 600;
      }
      .tag.up { background: var(--green-soft); color: var(--green); }
      .tag.flat { background: var(--stone); color: var(--muted); }
      footer {
        margin-top: 36px;
        padding-top: 18px;
        border-top: 1px solid var(--mist);
        color: var(--faint);
        font-size: 13px;
      }
      @media (max-width: 640px) {
        .sheet { margin: 0; border-radius: 0; border-left: 0; border-right: 0; }
        .stats { grid-template-columns: 1fr; }
        body { font-size: 15px; }
      }
    </style>
  </head>
  <body>
    <article class="sheet">
      <!-- deliverable content: eyebrow, h1, lead, meta, stats, h2 sections,
           lists, callout, table, footer — use only what the deliverable needs -->
    </article>

    <script>
      // Theme follows its host automatically — no manual toggle.
      // • Inside Plank it tracks the app's theme (which can differ from the OS)
      //   and updates live when you switch theme while the file is open.
      // • Standalone (downloaded or shared) it follows the OS setting and
      //   reacts live if you change the OS theme.
      (function () {
        var root = document.documentElement;
        // App-supplied theme; when set it wins over the OS preference.
        var override = null;
        var mql =
          window.matchMedia &&
          window.matchMedia("(prefers-color-scheme: dark)");
        function apply() {
          root.setAttribute(
            "data-theme",
            override || (mql && mql.matches ? "dark" : "light"),
          );
        }
        apply();
        if (mql) {
          var onChange = function () { apply(); };
          if (mql.addEventListener) mql.addEventListener("change", onChange);
          else if (mql.addListener) mql.addListener(onChange);
        }
        window.addEventListener("message", function (e) {
          var d = e && e.data;
          if (
            d &&
            d.type === "plank:set-theme" &&
            (d.theme === "dark" || d.theme === "light")
          ) {
            override = d.theme;
            apply();
          }
        });
      })();
    </script>
  </body>
</html>

Images and file names

Two small rules keep HTML deliverables portable and easy to find. They apply to every HTML file you build, not just dashboards.

Reference images by a path relative to the HTML file's own folder. chart.png loads from the same folder, images/q4.png from a subfolder, ../shared/logo.png from one level up. Paths are not relative to the workspace root, and a leading slash (/chart.png) resolves against the file's own folder too — not the root. So place the image beside the HTML (or in a subfolder) and point at it relatively; an absolute or root-relative URL silently fails to load. (For dashboards, Interactive HTML dashboards has the full asset rules.)

Give every HTML file a descriptive, content-specific nameq4-revenue-report.html, acme-invoice.html — never a generic index.html. A workspace accumulates many HTML files across many folders, and there's no index.html default-entry convention here; the name is what makes a file easy to tell apart in file lists and search.

Check the Russian before you hand it over

A Russian document with "прямые кавычки" where «ёлочки» belong, a hyphen standing in for a dash, and ё left out reads as foreign however well it is written. None of that is a matter of taste, so it is checked rather than remembered — by the same tool that checks decks, which takes an HTML file too:

mkdir -p /workspace/scripts/deck
curl -sSfL -o /workspace/scripts/deck/plank_deck_qa.py https://plank.md/help/assets/plank_deck_qa.py
python3 /workspace/scripts/deck/plank_deck_qa.py квартальный-отчёт.html

Nothing is rendered in that mode, so it needs no LibreOffice: it reads the visible text, ignores <script>, <style> and <code>, and reports quotation marks, dashes, ranges, number formatting, non-breaking spaces and missing ё. It exits non-zero on an error, so it drops into the same build → check → repair loop. What it cannot check is the writing itself — see Checking the deck for what is deliberately left out and why.

The same command also checks any diagram in the file — an inline <svg> painted in a literal colour instead of a token, or missing its accessible name, is an error there too, on an English document as much as a Russian one. Those rules are on Diagrams; fix what it reports the same way.

Prefer something else?

This is a default, not a rule. You're always in control of the format:

  • Want markdown (or a PDF, or Word)? Just say so — "give me this as markdown" — and that's what you'll get.
  • Have your own house style or an HTML template? Point Plank at it and it will use yours instead.
  • Want it to stick for a whole workspace? Add a line to that workspace's AGENTS.md — for example "Deliverables: plain markdown" or "Deliverables: use templates/report.html". Plank reads AGENTS.md and follows your preference over this default, every time, in that space.