Files
chart-sins/CLAUDE.md
T
Claude 458ccaeb17 Retitle sins to name the problem plainly
Titles are scanned in the gallery and recognized in a link, so they
should say what the sin is rather than pun about it. Retitles the three
published sins and the backlog's working titles, and records the rule in
CLAUDE.md: the title names the problem, the poke carries the voice.

Filenames are untouched, so existing URLs still resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULE5RRxdQE1ebwefEd2eCM
2026-08-05 16:48:33 +00:00

5.7 KiB
Raw Blame History

Chart Sins — working notes

Conventions and gotchas for anyone (human or agent) working in this repo. Keep this current when a convention changes.

What this is

Chart Sins is "nohello.net for bad charts." The unit of value is a single, linkable, cited page you send someone instead of re-explaining, for the hundredth time, why their chart misleads. The gallery is secondary; the per-sin page is the product.

Each sin page follows one shape: poke → proof → why → the fix → receipts → share.

Voice & content conventions

  • The title names the problem plainly; the poke carries the voice. A title is a label someone scans in the gallery and recognizes in a link — write "Truncated Y-Axis on a Bar Chart," not "The Truncated Temptation." Use the field's standard term for the sin where one exists. Save the wit for the poke; that's what it's for.
  • The poke names the general sin, never the sample's specifics. A visitor didn't "start the axis at 90" — they mis-set the baseline. Write "Your bars don't start at zero…", not the numbers from our example data.
  • Tone: cheeky, not moralizing. "Caught — here are the receipts," not "repent." Poke the mistake, keep it light.
  • Same numbers, both charts. The bad and fixed charts must share one dataset so the page proves dishonest-vs-honest by construction. Charts are authored as Vega-Lite specs and rendered at build time — never screenshots.
  • The bad chart is intentionally wrong; the fixed chart must obey the rules. (e.g. the dual-axis "sin" deliberately breaks the one-axis rule; its fix uses separate honest panels.) When picking chart colors or building a new chart, run the dataviz skill and validate the palette with its validator.
  • Citations are core. Every sin cites the canon (src/lib/references.ts) under "Don't take our word for it." Don't invent authority — point at it.
  • Internal build decisions stay out of visitor-facing content and the DOM. The design system (Carbon), the typeface (IBM Plex), and the inspiration (nohello.net) are engineering choices — they must not appear in page copy, the footer, meta tags, or CSS class names on the public site. (They were scrubbed once already; keep them out.) Naming them here, in repo docs, is fine.

Adding a sin

  1. Author two Vega-Lite specs in src/charts/, e.g. my-sin-bad.json and my-sin-fixed.json. Same underlying data in both. Omit $schema/config; explicit width/height are fine. The bad one breaks a rule on purpose; the fixed one is correct.
  2. Add src/content/sins/my-sin.md with frontmatter (schema in src/content.config.ts):
    title, summary, poke, category, severity (15), tags[],
    badChart, fixedChart, date, draft, citations: [{ key, note }]
    
    badChart/fixedChart are the spec basenames (no .json). citations[].key must exist in src/lib/references.ts (build throws otherwise).
  3. New reference? Add it once to src/lib/references.ts, then cite its key.
  4. npm run dev and check. The OG card for the sin is generated automatically.

Design & rendering

  • Astro 5, static output, zero client JS — the one exception is the small progressive-enhancement copy-link script on the sin page.
  • Styling is a token layer (src/styles/global.css) built on Carbon design tokens: layered light/dark themes, 8px spacing scale, IBM Plex (self-hosted via @fontsource), square geometry, a 2px focus ring. Class prefix is site- (not cds-).
  • Charts render to static SVG at build via the shared pipeline in src/lib/renderChart.ts (used by both the on-page component and the OG generator, so they stay identical). Charts sit on a fixed light canvas (--chart-canvas) in both themes so the single baked SVG keeps AA contrast.
  • The categorical palette is Carbon's data-vis palette, validated for colorblind-safety/contrast on the chart surface. Re-validate if you change it.

OpenGraph cards

  • Per-sin 1200×630 cards are generated at build time — no browser. src/lib/og.ts composes with satori, rasterizes with @resvg/resvg-js; endpoints are src/pages/og/[slug].png.ts and og/default.png.ts.
  • Card content stays lean: poke + before/after charts + severity + category. No citation list on the card (that lives on the page).
  • Fonts: three IBM Plex TTF weights are committed under src/og/fonts/ (satori and resvg need real font files; the @fontsource woff2 won't do).
  • satori gotchas (all learned the hard way): empty <div>s in a flex row each need their own display:flex; don't put a quoted font-family in an inline style (it breaks the style parse — set the default font via satori's fonts instead); trim the markup so there's a single root node; give <img> dimensions in the style as px; cap charts by height so tall vconcat charts don't overflow.

Deploy & ops

  • GitHub Pages via .github/workflows/deploy.yml, triggered on push to main (plus workflow_dispatch). Project path is configured in astro.config.mjs (site + base/chart-sins).
  • Known gotcha — the default branch. The repo's default branch is currently the feature branch (it was the first branch pushed into the empty repo). The auto-created github-pages environment only allows deploys from the default branch, so a push to main builds but the deploy job is rejected. Until this is fixed we deploy by dispatching the workflow on the default branch. Fix: set the default branch to main (Settings → General); then plain pushes to main deploy normally and this note can go away.
  • Pages had to be enabled manually once (Settings → Pages → Source: GitHub Actions) — the Actions token can't create the Pages site itself.