Skip to content

Instantly share code, notes, and snippets.

@kriswill
Last active July 29, 2026 22:15
Show Gist options
  • Select an option

  • Save kriswill/c5274f3817c345f6d8e9f9366fa8e23a to your computer and use it in GitHub Desktop.

Select an option

Save kriswill/c5274f3817c345f6d8e9f9366fa8e23a to your computer and use it in GitHub Desktop.
session-report: Claude Code skill — self-contained HTML session reports with render-verified SVG/dataviz (MIT)

session-report — a Claude Code skill

Generates a rich, self-contained HTML session report (dark-themed, inline CSS/SVG, no network dependencies) plus a concise Markdown companion for LLM re-ingestion, written to $XDG_DOCUMENTS_DIR/session-reports/.

The report includes: a numbered problem→investigation→resolution timeline, a detailed tool-use log (including false starts), inline SVG architecture diagrams and CSS bar charts, a root-cause table, and overall observations. Complex visuals are render-verified headlessly before delivery — via Playwright, falling back to a connected Chrome DevTools MCP.

Install (macOS / Linux)

Needs only curl (no GitHub CLI, no auth). Installs into ~/.claude/skills/session-report/; asks [y/N] before overwriting an existing copy:

curl -fsSL https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.sh | bash

If a copy already exists it asks before overwriting (FORCE=1 skips the prompt). Prefer to read before you pipe? The same command without | bash prints the script.

Install (Windows)

Native PowerShell, using the same irm | iex pattern as Scoop/Bun/uv. Installs into %USERPROFILE%\.claude\skills\session-report\:

irm https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.ps1 | iex

$env:FORCE = '1' skips the overwrite prompt. To target an alternate skills folder, set the variable first — PowerShell has no pipeline scoping gotcha (see below), since iex runs in the current session:

$env:CLAUDE_SKILLS_DIR = "$HOME\.claude-me\skills"; irm https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.ps1 | iex

Alternatively, the bash one-liner above works as-is in Git Bash or WSL (Claude Code's native Windows install requires Git for Windows, so Git Bash is already present; ~/.claude/skills resolves to %USERPROFILE%\.claude\skills).

Targeting an alternate skills folder

The script honors CLAUDE_SKILLS_DIR: the skill lands in $CLAUDE_SKILLS_DIR/session-report instead of ~/.claude/skills/session-report. Useful when your Claude config lives somewhere non-default (e.g. a CLAUDE_CONFIG_DIR setup), or to install into a project's .claude/skills/.

Gotcha: the variable must be assigned on the bash side of the pipe. A leading VAR=value prefix applies only to the first command of a pipeline, so this does not work — curl gets the variable, but the bash that runs the script never sees it and falls back to ~/.claude/skills:

# ⚠️ WRONG — CLAUDE_SKILLS_DIR reaches curl, not bash ⚠️
CLAUDE_SKILLS_DIR=~/.claude-me/skills curl -fsSL https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.sh | bash

✅ Put the assignment immediately before bash instead:

curl -fsSL https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.sh | CLAUDE_SKILLS_DIR=~/.claude-me/skills bash

(FORCE=1 composes the same way: … | FORCE=1 CLAUDE_SKILLS_DIR=… bash.)

Manual install

The skill is deliberately flat (no subdirectories — matching what gists support), so the gist's file layout IS the installed layout. With the GitHub CLI it's a one-liner:

gh gist clone c5274f3817c345f6d8e9f9366fa8e23a ~/.claude/skills/session-report

This also gives you a git remote for pulling future updates (git pull inside the skill dir). Or copy the files by hand:

~/.claude/skills/session-report/
├── SKILL.md
├── template.html
├── components.md
└── LICENSE

(00-README.md and the install scripts are distribution extras — harmless to keep, safe to delete.)

Then invoke in Claude Code with /session-report (or just ask it to "write up this session").

License

MIT © Kris Williams — see LICENSE.

session-report — component & visual cheat-sheet

Copy-paste building blocks for the HTML report. All are self-contained (inline CSS/SVG). Classes are defined in template.html's <style> block — copy that block verbatim first.

Sections (in order)

  1. header + .chip metadata
  2. .lead summary
  3. Timeline of .step cards (.grid of k/v rows; optional .learn callout)
  4. Tool-use log of .toolcards (the detailed part — see below)
  5. Architecture diagram(s) — inline SVG
  6. Data-viz — inline SVG / CSS charts
  7. Root-cause table
  8. Overall observations
  9. footer

The tool-use log (make this rich)

The point of this section is the reasoning, not a command dump. For every notable tool use capture four things:

  • Why this tool — what made it the right instrument here, and what the alternatives were.
  • False starts — the thing tried first that failed, and the reason (wrong assumption, env quirk, auth, schema drift, rate limit, bad glob, ...). This is usually the most useful content in the whole report.
  • Commands — verbatim in a <pre>; redact secrets.
  • Outcome — what it yielded and how it advanced the work.
<div class="toolcard">
  <div class="name">Tool: <code>curl (CI orchestrator REST API)</code></div>
  <p class="why"><b>Why this tool:</b> the CLI lacked a job-inspect verb; the raw API returns the job
     state machine directly, which is what distinguishes "queued" from "picked up but stalled".</p>
  <div class="falsestart"><b>False start:</b> filtered workers by <code>filter[pool]</code> first — returned empty,
     which read like "no workers anywhere". The param name was wrong; listing all workers + <code>include=workers</code>
     on the pool showed 10 idle workers in the <em>staging</em> pool. Lesson: an empty filtered result is not proof of absence.</div>
  <pre>curl -s -H "Authorization: Bearer $TOKEN" \
  "https://HOST/api/v3/jobs/job-XXXX" | jq '.data.attributes.status'</pre>
  <div class="outcome"><b>Outcome:</b> confirmed the job was <code>pending</code> (never dispatched), redirecting
     the investigation from "bad token" to "no worker in this pool".</div>
</div>

Inline SVG architecture diagram

Hand-author node/edge graphs — offline, crisp, themeable. Mark the failing node with class="node hot" and the implicated edge with class="edge hot". Always give <title>/<desc> for accessibility. A reusable arrowhead marker (put once per SVG):

<defs>
  <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
    <path d="M0,0 L10,5 L0,10 z" fill="#9aa3b2"/>
  </marker>
</defs>
<rect class="node" x="20" y="70" width="150" height="60" rx="8"/>
<text class="nlabel" x="95" y="96" text-anchor="middle">Service</text>
<path class="edge" d="M170,100 L285,100"/>

For a FIFO queue / backlog, draw the slots as a row of small rects, fill drained ones with --good and the wedged head with --bad; label the buried target op.

Data-viz

Reach for a chart only when a number carries the story (backlog depth over time, time-per-phase, retry counts, op counts by type). Keep it inline.

  • Horizontal bars (categorical magnitudes): the .bar component in the template.
  • Timeline / gantt (when things happened): inline SVG <rect>s on a shared x = time axis.
  • Sparkline / trend: a single SVG <polyline>.

Follow the dataviz skill for form and color choices. Self-contained categorical palette (already in template :root; matches the dataviz default family):

var hex use
--c1 #6ea8fe series 1 / primary
--c2 #8fe0b0 series 2
--c3 #e6b455 series 3 / warning-ish
--c4 #c7a8ff series 4
--c5 #e6685c series 5 / bad
--c6 #5fd0d6 series 6

Rules: label directly (don't rely on color alone), sort bars by value unless order is semantic, start quantitative axes at zero, and add <title>/<desc> to every SVG chart.

Mermaid (optional, only if the user allows network)

Default is self-contained inline SVG. If the user is fine with a CDN fetch, a Mermaid diagram is:

<script type="module">
  import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
  mermaid.initialize({ startOnLoad:true, theme:"dark" });
</script>
<pre class="mermaid">flowchart LR; Trigger-->Worker-->Datastore;</pre>

Note in the report that it requires network to render, which breaks the offline-single-file property.

Reminders

  • Redact secrets in every <pre>.
  • No BMP Private-Use-Area glyphs in file content (they get stripped on write) — use plain text / standard Unicode / SVG shapes.
  • Keep the .md companion in sync with the .html.
# Install the session-report skill into ~\.claude\skills\. The skill is flat
# (no subdirectories), so this is just four straight downloads. If a copy
# already exists it asks before overwriting ($env:FORCE = '1' skips the prompt).
# Usage: irm https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.ps1 | iex
$ErrorActionPreference = 'Stop'
$Raw = 'https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw'
$Base = if ($env:CLAUDE_SKILLS_DIR) { $env:CLAUDE_SKILLS_DIR } else { "$HOME/.claude/skills" }
$D = "$Base/session-report"
if ((Test-Path "$D/SKILL.md") -and $env:FORCE -ne '1') {
# Under `irm | iex` stdin is not the pipe, so Read-Host can prompt on the
# console; guard for genuinely non-interactive hosts (CI, redirected stdin).
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$reply = Read-Host "$D already exists. Overwrite? [y/N]"
if ($reply -notmatch '^(y|yes)$') { Write-Host 'aborted - nothing written.'; return }
} else {
# `return`, never `exit`: under iex, exit would close the user's shell.
Write-Host "$D already exists and no console to ask on - re-run with `$env:FORCE='1' to overwrite."
return
}
}
New-Item -ItemType Directory -Force -Path "$D" | Out-Null
irm "$Raw/SKILL.md" -OutFile "$D/SKILL.md"
irm "$Raw/template.html" -OutFile "$D/template.html"
irm "$Raw/components.md" -OutFile "$D/components.md"
irm "$Raw/LICENSE" -OutFile "$D/LICENSE"
Write-Host "session-report skill installed to $D"
#!/usr/bin/env bash
# Install the session-report skill into ~/.claude/skills/. The skill is
# flat (no subdirectories), so this is just four straight downloads — or skip
# this script entirely and `gh gist clone` the gist into the skills dir.
# If a copy already exists it asks before overwriting (FORCE=1 skips the prompt).
# Usage: curl -fsSL https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw/install.sh | bash
set -euo pipefail
RAW=https://gist.githubusercontent.com/kriswill/c5274f3817c345f6d8e9f9366fa8e23a/raw
D="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}/session-report"
if [ -e "$D/SKILL.md" ] && [ "${FORCE:-0}" != "1" ]; then
# stdin is the pipe when run via `curl … | bash`, so prompt on the
# terminal. Probe by actually opening /dev/tty — the node can exist yet be
# unopenable in a session with no controlling terminal.
if ( exec < /dev/tty > /dev/tty ) 2>/dev/null; then
printf '%s already exists. Overwrite? [y/N] ' "$D" > /dev/tty
IFS= read -r reply < /dev/tty || reply=""
case "$reply" in
[yY]|[yY][eE][sS]) ;;
*) echo "aborted — nothing written." > /dev/tty; exit 1 ;;
esac
else
echo "$D already exists and no terminal to ask on — re-run with FORCE=1 to overwrite." >&2
exit 1
fi
fi
mkdir -p "$D"
curl -fsSL "$RAW/SKILL.md" > "$D/SKILL.md"
curl -fsSL "$RAW/template.html" > "$D/template.html"
curl -fsSL "$RAW/components.md" > "$D/components.md"
curl -fsSL "$RAW/LICENSE" > "$D/LICENSE"
echo "session-report skill installed to $D"
MIT License
Copyright (c) 2026 Kris Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Error in user YAML: (<unknown>): mapping values are not allowed in this context at line 2 column 659
---
name: session-report
description: Generate a rich, self-contained HTML session report plus a concise Markdown companion in the user's XDG Documents folder. Use when the user says "create a session report", "write up this session", "document this session", "generate a report for what we did", or invokes /session-report. Produces a numbered timeline (problem -> investigation -> solution), a DETAILED tool-use log (why each tool was chosen, false starts, the actual commands run, and the outcome), inline SVG architecture diagrams and data-viz that illustrate the component/concept relationships exercised during the session, and an overall-observations conclusion. Be expressive: include whatever session material best tells the story.
---

session-report

Produce two artifacts describing the current session:

  1. <name>.html — a self-contained, styled report (inline CSS + inline SVG, no network dependencies) meant for a human to read.
  2. <name>.md — a concise Markdown companion of the same base name, optimized for fast LLM re-ingestion.

Both go in the user's XDG Documents directory (see resolution below). Same base name, two extensions.

This skill is about communication. Reconstruct what actually happened from the conversation and tell it clearly: the problems, how they were investigated and solved, which tools were reached for and why, and what the session reveals about the architecture. Lean into diagrams and data-viz where they make a relationship clearer than prose would.

When to use

  • The user asks to document / write up / report on the current (or a just-finished) session.
  • After a substantial debugging, investigation, or build session where a durable record is useful.

Don't use for: a quick answer recap in-chat (just answer), or committing docs into a project repo (that's a normal file edit, not this personal-report skill).

Step 1 — Resolve the output directory

Resolve XDG Documents robustly (macOS usually has no XDG env set):

docs_dir="${XDG_DOCUMENTS_DIR:-}"
if [ -z "$docs_dir" ] && [ -r "$HOME/.config/user-dirs.dirs" ]; then
  docs_dir=$(. "$HOME/.config/user-dirs.dirs" 2>/dev/null; printf '%s' "$XDG_DOCUMENTS_DIR")
fi
docs_dir="${docs_dir:-$HOME/Documents}"
mkdir -p "$docs_dir/session-reports"

Write both files into $docs_dir/session-reports/.

Step 2 — Name the report

session-report-YYYY-MM-DD-<slug>.{html,md} where <slug> is a short kebab-case topic drawn from the session (e.g. orchestration-stalls, auth-token-rotation). Use the real current date. If a file with that name exists, append -2, -3, ... rather than overwriting.

Step 3 — Reconstruct the session faithfully

Walk the conversation start to finish and extract:

  • The discrete steps / phases — each with its problem, what was investigated, and the resolution (or that it's still open).
  • Every meaningful tool use — not just the winners. Capture false starts and dead ends; they are often the most instructive part.
  • Architecture / component relationships that were exercised — services, queues, workflows, data stores, external systems, and how they connect.
  • Root causes and learnings, especially where a different process/architecture would have avoided the issue.

Accuracy rules: report only what actually happened. If something is uncertain, mark it as such — never invent commands, outputs, timestamps, or identifiers. Redact secrets (tokens, keys, passwords, full DSNs) — show only non-sensitive identifiers.

Step 4 — Author the HTML

Start from template.html (in this skill dir) — it carries the dark theme, layout, and a component library. Copy its <style> block verbatim so reports look consistent, then assemble the body from these sections (skip any that don't apply, add others if they help):

  1. Header + summary — one-paragraph .lead framing what the session was and its outcome.
  2. Timeline — one .step card per phase, numbered, each with: Problem, Action/Investigation, Finding, and (where earned) an inline .learn callout for an architecture insight.
  3. Tool-use log — this is a first-class section, not an afterthought. For each notable tool use, a .toolcard with:
    • What & why — the decision and the reasoning for choosing this tool over alternatives.
    • False starts — what was tried first and failed, and why it failed (wrong assumption, env quirk, schema drift, auth, etc.). Use the .falsestart block.
    • Commands — the actual commands/tool calls exercised, verbatim, in a <pre> (redacted as needed).
    • Outcome — what it produced and how it moved the investigation.
  4. Diagrams — inline SVG (see components.md) showing the component relationships exercised: boxes for services/stores/queues, arrows for data/control flow, and highlight the node(s) where the failure lived.
  5. Data-viz — where a number tells the story (e.g. queue backlog depth over time, time-per-phase, retries), render a small inline SVG/CSS chart. Follow the dataviz skill for color/mark choices; a validated categorical palette is duplicated in components.md so the report stays self-contained. Hand-positioned SVG (queues, timelines, node/edge diagrams) is easy to get subtly wrong — verify it renders coherently in Step 4.5.
  6. Root causes & alternative architectures — a table mapping symptom -> root cause -> the process/architecture change that would have prevented it.
  7. Overall observations — conclude with the cross-cutting themes for the whole session.

Be expressive with the material — the component library is a floor, not a ceiling. Add sections, callouts, or visuals that clarify this particular session.

Step 4.5 — Render-verify complex visuals (Playwright or Chrome DevTools MCP)

Hand-authored SVG/CSS dataviz can look correct in markup but overlap, clip, or overflow once a real browser lays out the text (long labels colliding with adjacent shapes, text spilling past its viewBox, bars running off their track). Markup review does not catch this — a render does. (Real example: an inline .fill span whose width was silently ignored rendered every bar chart empty; only the screenshot caught it.)

When to run: only when the report has non-trivial visuals — multiple SVG figures, dense/long labels, a queue/timeline/gantt diagram, or any chart where element positions are hand-computed. Skip for text-only or single-simple-chart reports.

Renderer preference order — never block the report:

  1. Playwright via bunx if bun is installed, else npx — do not dig a playwright binary out of some other repo's node_modules. Try --channel chrome first: it drives the system Chrome — or a Chrome-shimmed browser like Helium — with zero downloads and no revision pinning. Only then fall back to the bare invocation: bunx resolves the latest Playwright CLI, which demands its own exact pinned browser revision, and a cache populated by other projects' older installs will NOT satisfy it (fails with "Executable doesn't exist … run playwright install" — this is version pinning, not a browser-integration conflict). Don't run playwright install (pulls ~150MB) unless the user asks.
  2. Chrome DevTools MCP fallback. If Playwright errors that no browser is installed (or neither runner exists) and a Chrome DevTools MCP server is connected in the session (mcp__chrome-devtools__* tools), verify with it instead — it is a full real-browser render and just as valid: new_page on the report's file:// URL, take_screenshot (save to a scratch path, then Read/view the PNG), scroll figures into view with evaluate_script (el.scrollIntoView()) and re-screenshot per section as needed. evaluate_script is also handy for targeted probes, e.g. getComputedStyle(el).width to confirm a fill actually has extent.
  3. Neither available: skip this step and note in the final report that visuals were not render-verified.
scratch="$(mktemp -d)"
file="file://$docs_dir/session-reports/<name>.html"

# Pick a runner: bunx preferred, npx fallback.
if   command -v bun >/dev/null; then RUN="bunx"
elif command -v npx >/dev/null; then RUN="npx --yes"
else echo 'no bunx/npx -> skip render-verify'; fi

# Method A (primary, portable) — full-page screenshot, then VIEW the PNG.
# --channel chrome = system Chrome / Chrome-shim (Helium): no download, no pinning.
# Bare fallback needs the CLI's exact pinned browser revision in the cache.
$RUN playwright screenshot --channel chrome --full-page "$file" "$scratch/report.png" \
  || $RUN playwright screenshot --full-page "$file" "$scratch/report.png"   # both fail -> DevTools MCP fallback

Then view report.png (read it as an image) and scan every figure for: text overlapping shapes or other text, labels clipped at a viewBox edge, shapes/bars overflowing their container, and color-only encoding with no label. For a close-up of one dense figure (0-indexed <figure>), Method B crops just that element at 2x. It needs the playwright module in scope, so run it under bun (auto-installs from cache; --install=fallback sidesteps a stray ~/node_modules):

cat > "$scratch/shot.mjs" <<'EOF'
import { chromium } from 'playwright';
const [,, url, out, nth] = process.argv;
// system Chrome / Chrome-shim (Helium) first — no pinned-revision download
const b = await chromium.launch({ channel: 'chrome' }).catch(() => chromium.launch());
const p = await b.newPage({ viewport: { width: 1000, height: 900 }, deviceScaleFactor: 2 });
await p.goto(url, { waitUntil: 'networkidle' });
const el = nth != null ? p.locator('figure').nth(Number(nth)) : p.locator('body');
await el.screenshot({ path: out });
await b.close();
EOF
bun run --install=fallback "$scratch/shot.mjs" "$file" "$scratch/fig.png" 1   # 1 = second figure
# (npx-only environments: skip the crop and just inspect the full-page report.png from Method A.)

If the render looks wrong, redesign the SVG and re-render — loop until coherent. Common fixes: stack labels onto their own vertical rows instead of one horizontal line; grow the viewBox height to make room; shorten or wrap long label text; add a gap/ellipsis between crowded groups; move a caption below the shapes rather than beside them. Re-run the screenshot after each change and re-inspect. Only the final HTML on disk needs to be correct — the PNGs are throwaway (they live in $scratch).

Step 5 — Author the concise Markdown

Same content, compressed for an LLM: front-matter-style metadata block, a timeline table, a tool-use table (tool | why | false starts | outcome), the root-cause table, and the observations as bullets. Keep diagrams as short textual descriptions or fenced ASCII where useful. Keep it in sync with the HTML.

Step 6 — Report back

Give the user both file paths and a one-line summary of what's inside. If the report has complex visuals, state whether they were render-verified (Step 4.5) — and by which renderer (Playwright or Chrome DevTools MCP) — or that verification was skipped because neither was available. Offer to open the HTML (open <path> on macOS).

Guardrails

  • Self-contained by default. Inline all CSS and SVG; no external <script>/<link>/CDN fetches unless the user explicitly wants them (e.g. Mermaid via CDN). A report should render offline from a single file.
  • No secrets. Redact tokens/keys/passwords/DSNs; keep only non-sensitive IDs.
  • ASCII-safe glyphs. Do not paste nerd-font / Private-Use-Area glyphs into file content — they get stripped on write. Use plain text, standard Unicode (arrows -> or U+2192), or inline SVG shapes for icons.
  • Accessibility. SVG diagrams/charts get <title>/<desc>; keep text contrast high; don't encode meaning by color alone (add labels).
  • Faithful, not flattering. Include the false starts and the things left unresolved. The value is an honest record.
  • Render-verify complex visuals. When a report has non-trivial SVG/CSS dataviz, headlessly render it (Step 4.5) and visually confirm nothing overlaps, clips, or overflows before delivering — redesign and re-render if it does. Prefer Playwright, fall back to a connected Chrome DevTools MCP; skip gracefully only when neither is available.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Session Report — TITLE (DATE)</title>
<!--
session-report template. Copy this <style> block verbatim into every report.
Fill the body from the component blocks below (search "COMPONENT:").
Self-contained: inline CSS + inline SVG only, no network dependencies.
-->
<style>
:root {
--bg:#0f1117; --panel:#171a23; --panel2:#1e222d; --ink:#e6e9ef; --muted:#9aa3b2;
--line:#2a2f3c; --accent:#6ea8fe; --good:#57d38c; --warn:#e6b455; --bad:#e6685c;
--code:#0b0d12; --chip:#232838;
/* categorical palette (validated in components.md; swap for brand if desired) */
--c1:#6ea8fe; --c2:#8fe0b0; --c3:#e6b455; --c4:#c7a8ff; --c5:#e6685c; --c6:#5fd0d6;
}
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--ink);
font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
.wrap { max-width:960px; margin:0 auto; padding:40px 24px 80px; }
h1 { font-size:28px; margin:0 0 6px; letter-spacing:-.01em; }
h2 { font-size:20px; margin:44px 0 14px; padding-bottom:8px; border-bottom:1px solid var(--line); }
h3 { font-size:16px; margin:0 0 4px; }
.sub { color:var(--muted); margin:0 0 4px; }
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
code { background:var(--code); border:1px solid var(--line); border-radius:4px; padding:1px 5px;
font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; color:#cdd6e4; }
pre { background:var(--code); border:1px solid var(--line); border-radius:8px; padding:14px 16px;
overflow:auto; font:12.5px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; color:#cdd6e4; }
.meta { display:flex; flex-wrap:wrap; gap:8px; margin:16px 0 8px; }
.chip { background:var(--chip); border:1px solid var(--line); border-radius:999px; padding:3px 11px;
font-size:12.5px; color:var(--muted); }
.lead { background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--accent);
border-radius:8px; padding:16px 18px; margin:20px 0; }
.step { background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:18px 20px; margin:0 0 16px; }
.step .n { display:inline-block; min-width:26px; height:26px; line-height:26px; text-align:center;
background:var(--accent); color:#0b0d12; border-radius:6px; font-weight:700; font-size:13px; margin-right:8px; }
.grid { display:grid; grid-template-columns:120px 1fr; gap:6px 14px; margin-top:12px; }
.grid .k { color:var(--muted); font-size:12.5px; text-transform:uppercase; letter-spacing:.04em; padding-top:2px; }
.grid .v { margin:0; } .grid .v ul { margin:0; padding-left:18px; }
.toolcard { background:var(--panel2); border:1px solid var(--line); border-radius:10px; padding:16px 18px; margin:0 0 14px; }
.toolcard .name { font-weight:700; } .toolcard .name code { font-size:12.5px; }
.toolcard .why { color:var(--ink); margin:6px 0 10px; }
.falsestart { background:rgba(230,104,92,.08); border:1px solid rgba(230,104,92,.35); border-left:3px solid var(--bad);
border-radius:0 6px 6px 0; padding:8px 12px; margin:8px 0; font-size:14px; }
.falsestart b { color:var(--bad); }
.outcome { background:rgba(87,211,140,.08); border-left:3px solid var(--good); border-radius:0 6px 6px 0;
padding:8px 12px; margin:8px 0 0; font-size:14px; } .outcome b { color:var(--good); }
.learn { background:var(--panel2); border:1px solid var(--line); border-left:3px solid var(--warn);
border-radius:0 6px 6px 0; padding:10px 14px; margin-top:12px; font-size:14px; } .learn b { color:var(--warn); }
.tools { display:flex; flex-wrap:wrap; gap:6px; }
.tool { background:var(--panel2); border:1px solid var(--line); border-radius:6px; padding:2px 8px;
font:12px ui-monospace,monospace; color:#b9c2d0; }
figure { margin:18px 0; } figcaption { color:var(--muted); font-size:12.5px; margin-top:6px; text-align:center; }
.diagram { background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:18px; }
.good { color:var(--good); } .bad { color:var(--bad); } .warn { color:var(--warn); }
table { border-collapse:collapse; width:100%; margin:8px 0; font-size:13.5px; }
th,td { border:1px solid var(--line); padding:7px 10px; text-align:left; vertical-align:top; }
th { background:var(--panel2); color:var(--muted); font-weight:600; }
.bar { display:flex; align-items:center; gap:10px; margin:5px 0; font-size:13px; }
.bar .lbl { width:150px; color:var(--muted); text-align:right; }
.bar .track { flex:1; background:var(--code); border-radius:5px; overflow:hidden; height:16px; }
/* display:block is load-bearing: .fill is a span, and width is IGNORED on
inline elements — without it every bar renders empty (found via a real
render-verify pass; markup review does not catch this). */
.bar .fill { display:block; height:100%; border-radius:5px; }
footer { margin-top:40px; color:var(--muted); font-size:12.5px; border-top:1px solid var(--line); padding-top:16px; }
/* svg diagram helpers */
.node { fill:var(--panel2); stroke:var(--line); } .node.hot { stroke:var(--bad); stroke-width:2; }
.nlabel { fill:var(--ink); font:12px -apple-system,sans-serif; } .nsub { fill:var(--muted); font:10px ui-monospace,monospace; }
.edge { stroke:var(--muted); stroke-width:1.5; fill:none; marker-end:url(#arrow); }
.edge.hot { stroke:var(--bad); }
</style>
</head>
<body>
<div class="wrap">
<!-- COMPONENT: header -->
<h1>Session Report — TITLE</h1>
<p class="sub">One-line subtitle / scope</p>
<div class="meta">
<span class="chip">Date: YYYY-MM-DD</span>
<span class="chip">Env: ...</span>
<span class="chip">Outcome: ...</span>
</div>
<!-- COMPONENT: lead/summary -->
<div class="lead"><b>Summary.</b> What the session was, and how it ended.</div>
<h2>Timeline</h2>
<!-- COMPONENT: timeline step -->
<div class="step">
<h3><span class="n">1</span> Step title</h3>
<div class="grid">
<div class="k">Problem</div><div class="v">What was wrong / the goal.</div>
<div class="k">Action</div><div class="v">What was investigated or done.</div>
<div class="k">Finding</div><div class="v">What was learned.</div>
<div class="k">Tools</div><div class="v"><span class="tools"><span class="tool">tool-a</span><span class="tool">tool-b</span></span></div>
</div>
<div class="learn"><b>Architecture learning.</b> An insight this step surfaced.</div>
</div>
<h2>Tool-use log</h2>
<!-- COMPONENT: toolcard (detailed: why, false starts, commands, outcome) -->
<div class="toolcard">
<div class="name">Tool: <code>tool-name</code></div>
<p class="why"><b>Why this tool:</b> the reasoning for choosing it over alternatives.</p>
<div class="falsestart"><b>False start:</b> what was tried first, and why it failed (wrong assumption / env quirk / auth / schema drift).</div>
<pre># the actual command(s) exercised, verbatim (redact secrets)
tool-name --flag value</pre>
<div class="outcome"><b>Outcome:</b> what it produced and how it moved things forward.</div>
</div>
<h2>Architecture exercised</h2>
<!-- COMPONENT: svg architecture diagram (nodes + edges; mark the failure node .hot) -->
<figure>
<div class="diagram">
<svg viewBox="0 0 720 200" width="100%" role="img" aria-labelledby="d1t d1d">
<title id="d1t">Component flow</title>
<desc id="d1d">Trigger to worker to datastore; the worker node is where the failure lived.</desc>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#9aa3b2"/>
</marker>
</defs>
<g>
<rect class="node" x="20" y="70" width="150" height="60" rx="8"/>
<text class="nlabel" x="95" y="96" text-anchor="middle">Trigger</text>
<text class="nsub" x="95" y="114" text-anchor="middle">event source</text>
<rect class="node hot" x="285" y="70" width="150" height="60" rx="8"/>
<text class="nlabel" x="360" y="96" text-anchor="middle">Worker</text>
<text class="nsub" x="360" y="114" text-anchor="middle">stalled here</text>
<rect class="node" x="550" y="70" width="150" height="60" rx="8"/>
<text class="nlabel" x="625" y="96" text-anchor="middle">Datastore</text>
<text class="nsub" x="625" y="114" text-anchor="middle">state</text>
<path class="edge hot" d="M170,100 L285,100"/>
<path class="edge" d="M435,100 L550,100"/>
</g>
</svg>
</div>
<figcaption>Figure 1 — component flow; red = where the failure lived.</figcaption>
</figure>
<h2>By the numbers</h2>
<!-- COMPONENT: horizontal bar chart (CSS; use palette vars for series color) -->
<div class="bar"><span class="lbl">Phase A</span><span class="track"><span class="fill" style="width:70%;background:var(--c1)"></span></span><span>7</span></div>
<div class="bar"><span class="lbl">Phase B</span><span class="track"><span class="fill" style="width:40%;background:var(--c2)"></span></span><span>4</span></div>
<div class="bar"><span class="lbl">Phase C</span><span class="track"><span class="fill" style="width:20%;background:var(--c3)"></span></span><span>2</span></div>
<h2>Root causes &amp; alternative architectures</h2>
<table>
<tr><th>Symptom</th><th>Root cause</th><th>Alternative architecture</th></tr>
<tr><td>...</td><td>...</td><td>...</td></tr>
</table>
<h2>Overall observations</h2>
<ul>
<li>Cross-cutting theme for the whole session.</li>
</ul>
<footer>Generated YYYY-MM-DD. Companion: <code>&lt;name&gt;.md</code>.</footer>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment