| name | canon-slides |
| description | Generates Marp slide decks from canon knowledge. Use when asked to create slides, build a presentation, or generate a deck on a canon topic (context management, evaluator pattern, or skill authoring). Renders to HTML and PPTX for browser viewing and SharePoint/PowerPoint distribution. |
| category | ops |
| tags | ["slides","marp","presentations","knowledge"] |
| argument-hint | [topic] |
| hidden | true |
canon-slides
Generates a Marp slide deck from canon source files and renders it to HTML and PPTX.
Usage
/canon-slides <topic>
Topics:
| Slug | What it covers |
|---|
context-management | Token efficiency, always-on vs on-demand loading, context bloat patterns |
evaluator-pattern | Generator-evaluator separation, clean context eval, avoiding self-grading |
skill-authoring | Skill structure, description quality, eval coverage, gotchas pattern |
Steps
1. Parse arguments. Extract the topic slug from $ARGUMENTS. If the topic is not one of the three above, stop and list the valid options.
2. Read source files for the topic.
For context-management, read:
standards/efficiency.md (Token Efficiency section)
skills/context-check/SKILL.md
skills/sprint/SKILL.md (the always-on vs on-demand loading distinction)
For evaluator-pattern, read:
skills/sprint/reference/eval.md
skills/skill-eval/SKILL.md
DECISIONS.md (entries mentioning "evaluator", "generator-evaluator", or "clean context")
For skill-authoring, read:
standards/skill-setup-std.md
docs/agent-playbook.md (Skill lifecycle section)
skills/skill-eval/SKILL.md (the evals and testing section)
2b. Check for applicable partials. Before writing slides, read skills/canon-slides/partials/ and list available partials. If a partial fits the deck's topic or audience, read it and insert it verbatim at the appropriate position — do not rewrite or inline its content by hand.
| Partial | When to include |
|---|
worthwhile-harness.md | Any intro or pitch deck where the audience needs to understand what a harness must do before seeing canon specifically. Insert after the problem slide, before the canon overview. |
Marp has no native include syntax. The DRY contract is: edit partials, not decks. If a slide in a deck was sourced from a partial, add a HTML comment above it: <!-- partial: worthwhile-harness.md -->. Future edits go to the partial; the deck is regenerated.
3. Write posts/slides/<topic>.md. Create the file with this header:
---
marp: true
theme: canon
paginate: true
html: true
---
html: true is required — without it, all <div> styling is stripped by Marp.
Card/step reveal uses Marp's native fragment feature — do not hand-roll CSS animation/transition for this. Add data-marpit-fragment="N" (sequential per slide, starting at 1) to each card/step element:
<div class="card" data-marpit-fragment="1" style="...">First point</div>
<div class="card" data-marpit-fragment="2" style="...">Second point</div>
<div class="card" data-marpit-fragment="3" style="...">Third point</div>
Marp's bespoke viewer discovers every [data-marpit-fragment] element in a slide via querySelectorAll (in document order — the attribute's value doesn't matter, only its presence and DOM position) and reveals them one at a time as the viewer advances (space/arrow), only moving to the next slide once every fragment on the current one is revealed. This is what "slide in one-by-one" actually means for a Marp deck: a click-through build, not a timed auto-cascade. PPTX/print output ignores fragment state and always shows every fragment fully revealed (there's a dedicated print media-query rule for this) — expected, not a bug.
Do not attempt a custom CSS-only reveal keyed off the active slide. Two independent things break it, and both are hard to notice until you actually navigate the deck rather than just looking at a single rendered slide: (1) any <style> block written in the deck's own markdown gets auto-scoped by Marpit — every selector is rewritten to require being a descendant of the current section (e.g. .card becomes ... > section .card), so a selector like svg.bespoke-marp-active .card — which needs .card to be a descendant of the ancestor <svg> that actually receives the active-slide class — can never match; it's asking for a nested svg inside the section that doesn't exist. Moving the same rule into the theme CSS file doesn't help — theme CSS gets the identical per-section scoping. (2) Even a correctly-scoped @keyframes/animation gets its animation-name force-reset to a no-op by Marp's own bespoke CSS (svg.bespoke-marp-active.bespoke-marp-active-ready * { animation-name: __bespoke_marp__ !important; }) moments after the slide becomes active — so the animation is silently killed before it visibly plays. Native fragments sidestep both problems entirely, since they only need same-section attribute matching, which Marp's core (unscoped) fragment CSS already handles.
Space-filling — every slide uses a 3-row layout: heading / body / footer.
The section is display:flex; flex-direction:column; justify-content:flex-start with padding: 52px 72px 64px on a 720px canvas. Never use height:X% on body containers — percentage resolution against a flex container's padded size is browser-dependent and clips footers. Instead, use flex:1; min-height:0 on the direct child of the section (the body container). This fills exactly the remaining space after the h2 with no percentage guessing needed.
Single source of truth — use this pattern every time, without modification:
<div style="display:flex; flex-direction:column; flex:1; min-height:0; justify-content:space-evenly; gap:12px;">
</div>
<div style="display:grid; grid-template-rows:1fr auto; flex:1; min-height:0; gap:10px;">
<div style="display:flex; gap:14px; min-height:0;">
</div>
<div style="padding:10px 16px; background:rgba(62,64,71,0.3); border-left:3px solid #00FFFF; border-radius:0 8px 8px 0; font-size:0.8em; color:#B2B8C4;">
Footer callout text here.
</div>
</div>
<div style="display:flex; flex-direction:column; justify-content:center; flex:1; min-height:0; gap:24px;">
</div>
Font-size scaling rule. When a slide has dense content (4+ cards, code blocks + fix rows, or stacked diagrams), reduce font sizes proportionally rather than letting content overflow. Standard scale-down step:
| Content density | Card padding | Body text | Code text | Badge |
|---|
| Normal (2–3 items) | 16px 18px | 0.76em | 0.65em | 28px |
| Dense (4 items + fix rows) | 12px 14px | 0.68em | 0.60em | 22px |
| Very dense (6+ items) | 10px 12px | 0.62em | 0.56em | 18px |
Reduce gap between cards accordingly: gap:14px → gap:10px → gap:8px. Never clip or overflow — scale down first.
Cards containing code blocks (<pre> inside a card column): The body row flex container MUST have min-height:0, and each card MUST have min-height:0; overflow:hidden. Without these, grid's 1fr allocation is ignored and cards overflow into the footer. The <pre> inside the card gets flex:1; overflow:hidden so it fills but doesn't burst past the card boundary. Also drop stat numbers from 1.6em → 1.3em and body text from 0.76em → 0.72em to recover vertical space.
Rules:
flex:1; min-height:0 on every outer body container — no exceptions, no height:X%.
- Inside the body div, use
flex:1 on expanding children — never hardcode height:Npx on layout containers.
- Column layouts use
display:flex; align-items:stretch (not align-items:center) with flex:1 columns so they fill the available height.
- Footer callouts use
grid-template-rows:1fr auto — auto guarantees the footer its natural height; 1fr gives the body the rest.
- Never put the footer outside the
flex:1 container (orphaned elements render in a tiny strip below the content area).
- Vertical card lists (2–3 short cards) default to
justify-content:flex-start, which packs them at the top and leaves the remaining height as dead space below — flex:1 on the container only guarantees it can fill the slide, not that its children do. Use justify-content:space-evenly so leftover vertical space distributes as equal gaps around each card instead of collecting at the bottom. This is a pure CSS distribution, not a per-item size calculation — with 4+ cards or dense content the extra space per gap shrinks to nearly nothing on its own, so no different rule is needed at higher density.
Quick reference:
| Slide type | Outer container |
|---|
| Body + footer callout | display:grid; grid-template-rows:1fr auto; flex:1; min-height:0; gap:10px |
| Side-by-side columns | display:flex; align-items:stretch; flex:1; min-height:0; gap:20px |
| 2-column card grid | display:grid; grid-template-columns:1fr 1fr; flex:1; min-height:0; gap:16px |
| Vertical card list | display:flex; flex-direction:column; flex:1; min-height:0; justify-content:space-evenly; gap:12px |
| Flow/pipeline | display:flex; flex-direction:column; align-items:center; flex:1; min-height:0 |
| Title / hook (no h2) | display:flex; flex-direction:column; justify-content:center; flex:1; min-height:0 |
Orphan check — no elements outside the flex container. After writing each slide, verify that every visible element (blockquotes, captions, footnotes, closing remarks) lives inside the flex:1; min-height:0 body container — not after the closing </div>. Elements placed after the container render wherever remaining space allows, typically a tiny strip near the bottom. The pattern to avoid:
<div style="flex:1; min-height:0; display:flex; ...">
<div>big content</div>
</div>
<div style="font-size:0.85em; color:#6F7480;">caption here</div>
<div style="display:flex; flex-direction:column; justify-content:center; flex:1; min-height:0; gap:28px;">
<div>big content</div>
<div style="font-size:0.85em; color:#6F7480; text-align:center;">caption here</div>
</div>
For "big number + caption" hook slides specifically: use display:flex; flex-direction:column; justify-content:center; align-items:center; flex:1; min-height:0; gap:28px with both the number and its caption as direct children — this keeps them vertically centred together as a unit.
Slide structure — target 15–20 slides for a 20–30 min talk (~90 s/slide):
- Title slide — never use bare
# Heading + paragraph for the title slide. It renders top-left with huge empty space below. Use a full-height centered HTML layout instead:
<div style="display:flex; flex-direction:column; justify-content:center; flex:1; min-height:0; gap:24px;">
<div style="display:flex; flex-direction:column; gap:16px;">
<div style="font-size:3.2em; font-weight:900; line-height:1.1; color:#FFFFFF; letter-spacing:-0.02em;">Deck Title Here</div>
<div style="width:72px; height:4px; background:linear-gradient(90deg,#00FFFF,#4FFF00); border-radius:2px;"></div>
<div style="font-size:1.15em; color:#00FFFF; font-weight:400; line-height:1.5;">One-line hook subtitle goes here — no max-width, no <br></div>
</div>
</div>
- Hook / thought experiment — concrete scenario before any jargon (1 slide)
- Problem / Why this matters — what goes wrong without this pattern (1–2 slides)
- Core pattern — the key concept explained clearly (2–3 slides)
- Canon implementation — how canon handles this concretely, with specific examples from the source files (2–3 slides)
- Gotchas / What to watch for — failure modes and non-obvious constraints (1 slide)
- Summary — one slide, 3–5 bullet takeaways
- Q&A / Try it — one slide with a concrete next action
Visual design rules (learned from real-world conference decks):
Bullet lists → card grids. Never use bare <ul> for key points. Use a card grid instead:
<div style="display:flex; flex-direction:column; gap:12px; margin-top:16px;">
<div class="card c1" style="display:flex; gap:18px; align-items:center; padding:14px 20px;
background:rgba(62,64,71,0.35); border-radius:10px; border-left:4px solid #00FFFF;">
<div style="font-size:1.5em; font-weight:800; color:#00FFFF; min-width:28px; flex-shrink:0;">1</div>
<div style="font-size:0.88em; line-height:1.45;"><strong>Rule text here</strong> — explanation</div>
</div>
</div>
Two-column comparisons. Use bordered columns with a colored header strip:
<div style="flex:1; display:flex; flex-direction:column; border-radius:10px; overflow:hidden; border:1px solid rgba(0,255,255,0.25);">
<div style="padding:12px 18px; background:rgba(0,255,255,0.15); font-size:0.68em;
text-transform:uppercase; letter-spacing:0.09em; color:#00FFFF; font-weight:600;">Label</div>
<div style="flex:1; padding:16px; background:rgba(62,64,71,0.25);">content</div>
</div>
Diagrams (context window, stacks, timelines). The diagram column must be at least width:260px — narrow columns look decorative, not informative. Box labels at font-size:0.72em, numbers/token counts at font-size:1em; font-weight:800. The "smart zone" box uses flex:1 so it fills available height. Bottom reserved-area boxes show the number left-aligned in bold and the label right-aligned. Use justify-content:space-between when the number and label are on opposite sides; use margin-left:8px when they sit adjacent (otherwise Marp collapses them together with no gap):
<div style="padding:11px 16px; border-radius:4px; display:flex; justify-content:space-between; align-items:center;
background:rgba(244,102,0,0.22); border:1px solid #F46600;">
<span style="font-size:1em; font-weight:800; color:#F46600;">~33k</span>
<span style="font-size:0.68em; color:#F46600;">compaction reserve</span>
</div>
<div style="padding:11px 16px; border-radius:4px; display:flex; align-items:center;
background:rgba(244,102,0,0.22); border:1px solid #F46600;">
<span style="font-size:1em; font-weight:800; color:#F46600;">~33k</span>
<span style="font-size:0.68em; color:#F46600; margin-left:8px;">compaction reserve</span>
</div>
Claude Code context window constants (use these numbers in any context-management diagram):
- Total window: 200k tokens
- Output reserve: 32k (deduct from total → 168k input budget)
- Compaction reserve: ~33k (triggers at ~167k / 83% of window)
- System prompt baseline: ~14k (system instructions + built-in tool definitions, no CLAUDE.md)
- MCP schema cost: 5–10k per server (loads every session whether called or not)
- Cache reads: save cost (10% of base price) but still consume window space
SVG arrows in pipelines. Use inline SVG for colored arrows — never plain text → or ↓ for diagrams that need visual weight:
<svg width="20" height="20" viewBox="0 0 20 20">
<path d="M10 2 L10 14 M4 10 L10 16 L16 10"
stroke="#00FFFF" stroke-width="2" fill="none"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Never use <linearGradient> inside <defs> — Marp's parser breaks on <stop> tags and renders subsequent HTML as raw source.
SVG diagonal leader lines (callout annotations). Use when a label needs to point at the vertical midpoint of a tall diagram. Two diagonal lines converge from top-left and bottom-left to a horizontal tick, with the label to the right. The SVG uses preserveAspectRatio="none" with a 0-100 viewBox and vector-effect="non-scaling-stroke" to keep line weight consistent regardless of actual container dimensions:
<div style="position:relative; flex:1; font-size:0.78em; color:#6F7480;">
<svg style="position:absolute; top:0; left:0; width:100%; height:100%;"
viewBox="0 0 100 100" preserveAspectRatio="none">
<line x1="2" y1="0" x2="38" y2="50" stroke="#00FFFF" stroke-width="1.5" vector-effect="non-scaling-stroke" stroke-linecap="round"/>
<line x1="2" y1="100" x2="38" y2="50" stroke="#00FFFF" stroke-width="1.5" vector-effect="non-scaling-stroke" stroke-linecap="round"/>
<line x1="38" y1="50" x2="52" y2="50" stroke="#00FFFF" stroke-width="1.5" vector-effect="non-scaling-stroke"/>
</svg>
<div style="position:absolute; top:0; right:0; color:#B2B8C4; font-weight:600;">top label</div>
<div style="position:absolute; top:50%; left:55%; transform:translateY(-50%);">
<div style="font-size:1.8em; font-weight:800; color:#00FFFF; line-height:1;">168k</div>
<div style="font-size:0.82em; color:#00FFFF;">tokens</div>
</div>
<div style="position:absolute; bottom:0; right:0; text-align:right;">bottom label</div>
</div>
Cards with a level badge (L1 / L2 / L3, numbered, lettered). Never use a fixed width:Xpx column for a badge that also has a sublabel (Deterministic, Behavioural, etc.) — at Marp's scaled font size the text overflows into adjacent columns. Put the sublabel inline with the title instead:
<div style="font-size:1.6em; font-weight:800; color:#00FFFF; flex-shrink:0; min-width:36px; text-align:center;">L1</div>
<div style="flex:1;">
<div style="display:flex; align-items:baseline; gap:10px; margin-bottom:4px;">
<span style="font-size:0.85em; font-weight:700; color:#00FFFF;">Title</span>
<span style="font-size:0.6em; color:#6F7480; text-transform:uppercase; letter-spacing:0.06em;">Sublabel</span>
</div>
<div style="font-size:0.78em; color:#B2B8C4; line-height:1.5;">Description</div>
</div>
Weak-evidence / checklist rows. Each item in its own box (not a bare <li>):
<div class="card c1" style="display:flex; gap:14px; align-items:center; padding:13px 18px;
background:rgba(244,102,0,0.08); border-radius:8px; font-size:0.82em;">
<span style="color:#F46600; font-size:1.2em; flex-shrink:0;">✗</span>
<span>item text</span>
</div>
Guidelines for slide copy:
- Active voice, short sentences. Each slide has one point.
- Code blocks use fenced
``` — Marp renders them with syntax highlighting.
- Prefer concrete examples over abstract principles.
- Marp slide separator is
--- on its own line. Do not skip it between slides.
4. Render to HTML and PPTX. Prefer the repo render script:
npm run slides -- <topic>
It writes both:
posts/slides/<topic>.html
posts/slides/<topic>.pptx
The HTML render keeps Marp's interactive deck behavior and repo-specific hardening. The PPTX render is for SharePoint/PowerPoint distribution.
If you must run Marp manually, the HTML command is:
npx @marp-team/marp-cli posts/slides/<topic>.md \
--theme skills/canon-slides/themes/canon.css \
-o posts/slides/<topic>.html \
--allow-local-files \
--html \
--bespoke.transition=false
Omitting --html silently strips all <div> styling and the slides render as plain text.
The manual PPTX command is:
npx @marp-team/marp-cli posts/slides/<topic>.md \
--theme skills/canon-slides/themes/canon.css \
-o posts/slides/<topic>.pptx \
--allow-local-files \
--html \
--pptx
If npx is not available or marp-cli fails, stop and tell the user:
marp-cli not found. Install with: npm install -g @marp-team/marp-cli
Then rerun: npm run slides -- <topic>
5. Report output. On success, tell the user:
- Path to the
.md source: posts/slides/<topic>.md
- Path to the HTML:
posts/slides/<topic>.html
- Path to the PPTX:
posts/slides/<topic>.pptx
- How to open HTML locally:
open posts/slides/<topic>.html (macOS) or just open it in any browser
- How to use PPTX: upload
posts/slides/<topic>.pptx to SharePoint or open it in PowerPoint
Reserved zone — page counter
The page counter (section::after) is positioned at bottom: 16px; right: 48px of the section. It renders at roughly 60 px wide × 18 px tall at font-size: 0.5em. This puts it at approximately x: 1136–1232 px, y: 686–704 px on the 1280×720 canvas.
Never place content in the bottom-right corner of a slide. The safe boundary is: keep content above bottom: 48px and left of right: 120px relative to the section. Absolute-positioned elements anchored to bottom:0; right:0 of a content container will collide with the counter.
The page counter is in the bottom padding area (below the 604 px content zone), so flex/grid body content won't collide — only position:absolute elements with low bottom values can reach it.
Gotchas
- Never embed images as base64 data URIs in slide HTML. Large base64
src values cause GPU compositing artifacts: the browser caches the first slide's compositor layer and bleeds it through every subsequent slide. The first slide appears ghosted behind all others. Always use file references (<img src="./filename.png">) and place images in posts/slides/ alongside the HTML. The --allow-local-files flag is already required for the theme, so local image refs work without extra flags.
- Disable browser transitions for deck HTML. Marp's bespoke template keeps slides as stacked SVGs and browser compositing can leave inactive slides visually cached. Render with
--bespoke.transition=false; the repo render script also injects CSS that uses display:none for inactive slides in normal presentation views.
- No blank lines inside HTML blocks. Marp's markdown parser (markdown-it) exits HTML-block mode at the first blank line. Any content after a blank line inside a
<div> structure is re-parsed as markdown — so a 4-space-indented <div> after a blank line becomes a code block displaying raw HTML source. Rule: once you open a <div>, write all child tags continuously with no blank lines between them until the outermost closing </div>. Use a single blank line only between top-level slide elements (markdown headings, paragraphs, and top-level HTML blocks).
- Never use SVG
<defs> or <linearGradient> inside slide HTML. Marp's parser breaks on <stop style="..."> tags inside <defs>, rendering all subsequent HTML in that slide as raw source code in a code block. Use a single solid stroke color for SVG arrows instead — gradients are not worth the parse failure.
--html is not optional. Omitting it from the marp-cli command silently strips all <div> and <style> blocks — slides render as plain unstyled text even though html: true is in the frontmatter. Always pass both.
html: true in frontmatter alone is not enough — the CLI flag and frontmatter key must both be set.
- Diagram column width ≥ 260px. Narrower columns make the context-window and stack diagrams look decorative rather than readable. Use
width:260px; flex-shrink:0 for diagram columns.
- Numbered card sizes. Large number labels (
font-size:1.5em; font-weight:800) read clearly in a 1280×720 viewport. Anything smaller at 0.7em or below disappears in a projector room.
- Vertical card lists need
justify-content:space-evenly, not the flex-start default. flex:1; min-height:0 on the container only makes it able to fill the slide — with 2–3 short cards and no justify-content set, they pack at the top and leave a visible dead-space band below. space-evenly distributes the leftover height as equal gaps instead.
- Card/step reveal uses native
data-marpit-fragment="N" attributes, never hand-rolled CSS. See the explanation under step 3 — any custom animation/transition keyed off the active slide either can't match (deck <style> blocks and theme CSS are both auto-scoped per-section, so a selector needing the ancestor <svg>'s active-slide class can never reach it) or gets silently killed (Marp force-resets animation-name to a no-op on the active slide once "ready"). Fragments are Marp's own unscoped mechanism and don't hit either trap.
- Marp's
--- separators are load-bearing — they create slide breaks. Do not remove them when editing the .md source.
- The
--allow-local-files flag is required for CSS themes referenced by local path; without it, marp-cli silently ignores the theme.
- The
theme: value in the deck's frontmatter must match the theme name declared in the CSS (/* @theme canon */). Mismatch causes marp-cli to fall back to default styling.
- Run the render command from the repo root so relative paths to
skills/canon-slides/themes/ resolve correctly.
- If the deck opens blank in the browser, check the browser console — Content Security Policy on some browsers blocks local file CSS. Use
file:// URL or open on macOS.