Writing & editing blog posts — calculatethis.io
These rules govern every .md file in this directory. They auto-load when you work
here. They sit on top of the BlogOS pack in protocols/blog/ (research, SEO,
citations, E-E-A-T, per-type structure) — read that for depth. This file is the
canonical source for the computation-box format and the number-discipline rule;
where it and an older BlogOS skill disagree, this file wins.
Shared agent memory
- Claude reads this
CLAUDE.md; Codex reads the siblingAGENTS.md. Here,AGENTS.mdis a symlink to this file, so update thisCLAUDE.mdsource when blog guidance should be shared by both agents. - Do not use temporary
session.mdorHANDOFF.mdfiles for project tracking. Put durable blog/SEO decisions in../../docs/or the active plan at../../plan/improvement-plan-2026-07.md. - Root-level visual and workflow memory lives in
../../CLAUDE.md, whose siblingAGENTS.mdpoints to the same content for Codex.
The slash commands /b-write (draft a new post) and /b-review (audit + fix an
existing post) both enforce this file.
1. The prime directive: numbers and formulas leave the prose
Prose and headings carry words, not math. No worked arithmetic, no currency
figures, no result values, and no formula lines inside body paragraphs, list items,
or headings. Every computation, formula, and worked example lives in a fenced
```calc block placed at the end of the paragraph that introduces it.
- ❌ "You sell the first bracelet for $10 and a second, so revenue rises to $19 — a $9 gain."
- ✅ "Selling one more bracelet adds only what that sale brings in, not the full price." → then a
```calcblock with the figures.
What may stay inline:
- Symbolic variable names as inline code, sparingly:
MR,MC,ΔTR. These name concepts; they are not computations. - One exception for formulas: the opening answer blockquote (and, if needed, an
FAQ answer) may state the bare symbolic formula as inline code — e.g.
`MR = ΔTR ÷ ΔQ`— because it is the featured-snippet target. Never put worked numbers there; the formula only. Everywhere else, the formula goes in acalcblock.
Self-check before shipping: outside ```calc fences, the body should contain no
bare digits or $. Grep it (see §6).
2. The computation box (```calc)
A fenced block tagged calc. The text after calc on the fence line is an optional
caption, which renders as a header band on the box.
```calc Marginal revenue per journal
1 → 2 : 38 − 20 = $18
2 → 3 : 54 − 38 = $16
3 → 4 : 68 − 54 = $14
```
Rendered by lib/remark-calc.ts into a styled .calc-box (app/globals.css):
a two-tone light-accent card — a slightly darker header band over a lighter body,
mono text, dark-mode aware.
Grid alignment (write key = value)
If every non-blank line contains an =, the block renders as a two-column grid
split on the first =, so all the = line up automatically — you do not need
to hand-align with spaces. So write computations as label = value:
```calc Tutoring: the second session
First student = $25
Both students = $45
Marginal revenue = 45 − 25 = $20
```
- The value after the final
=on a line is auto-highlighted in the accent (blue). A value is a number, optionally signed /$-prefixed /%-suffixed. - A line may chain (
= 45 − 25 = $20); the first=aligns the grid, the last value is highlighted. - Blank lines inside the block become small spacers — use them to group.
Plain fallback
If any non-blank line lacks = (a rule, a comparison using →, a labelled list),
the block renders as a plain mono <pre> instead of a grid. Still no gray wrapper.
```calc The decision rule
MR > MC → keep going, each unit adds profit
MR = MC → the sweet spot, stop here
MR < MC → stop, each unit now loses money
```
Use a calc block for
The formula, every worked example, a short numeric schedule/table (write rows as
key = value, not a pipe table), unit conversions, and any step-through. Caption
each one. Keep a block focused — one idea per box, like a paragraph.
3. Post structure
- Answer blockquote first (
> …, 40–60 words) — the rule in words, formula as inline code allowed (the one exception, §1). No worked numbers. - Paired-calculator link immediately after, only if the calculator exists in
app/config/routeDefinitions.ts(else omit — never link a 404). - H2 sections — few, claim/question phrasing ("What marginal revenue measures",
not "Introduction"). Inside a section, lead a paragraph with a bold phrase
(
**Find the differences first.**) instead of spawning sub-headings. Each computation gets itscalcblock at the paragraph's end. ## Frequently asked questions— 3–5###questions (from People-Also-Ask). The renderer lifts this into the FAQ accordion and the post's white card; keep the exact heading shape so extraction works (lib/blog.ts).- Short close — one practical takeaway, no fluff, no second CTA.
Heading discipline
Few ##; bold lead-ins, not H3 soup. The old posts had a dozen ### per page
("A Simple Breakdown:", "Analogy:", "Quick Checklist:") — do not reproduce that. H3
only when an H2 has 2+ genuine sub-sections. FAQ questions are the expected ###.
4. Voice — clear and friendly, fluff cut
Beginner-accessible plain English. Concrete first, explanation after. Cut every
"Humor tip", "Reflective question", "Myth-Buster", and analogy-for-its-own-sake. Honor
the BlogOS anti-slop list (protocols/blog/scannable-formatting-skill.md): no
em-dash-as-crutch, no semicolons, no "Here's the thing:", no "Most people…", no
"powerful/ultimate/game-changing", no closing engagement-bait question.
5. Frontmatter & pipeline facts
Frontmatter contract is BlogOS's (do not add fields the renderer ignores):
---
title: '…' # H1 + <title> + og:title + JSON-LD headline, ≤ 70 chars
slug: kebab-case # equals filename without .md
excerpt: '…' # meta desc + dek, ≤ 160 chars, a real sentence
author: Ugo Charles
publishDate: YYYY-MM-DD
dateModified: 'ISO datetime' # bump on every edit
status: published
tags: [ … ]
featuredImage: /blog/<slug>.webp # see image sourcing below
---
Featured images come from Unsplash. Each post ships one featured image at /blog/<slug>.webp (1200×675 WebP, < 200 KB). Source it from a free Unsplash photo, record the pick in scripts/unsplash-images.json, and run npm run blog:unsplash <slug> to download, convert, and set featuredImage. Full method, licensing (free images.unsplash.com only — never plus.unsplash.com premium), and gotchas are in protocols/blog/media-and-images-skill.md → "Sourcing featured images from Unsplash".
Pipeline (so you know why the rules are shaped this way):
lib/blog.tsruns the body throughremark → remark-gfm → remark-calc → remark-htmlwithsanitize: false(our content is trusted — never author untrusted markdown here). It also regex-extracts the FAQ section into the accordion and computes wordcount, so keep the FAQ heading shape and don't hand-author FAQ HTML.remark-gfmis present, so GFM tables technically render — but for numeric data prefer acalcblock; reserve prose tables for non-numeric grids if ever needed.- No MDX, no math rendering (no
$…$), no auto heading IDs (no{#id}).
6. Quality gate (every post, before shipping)
Run the BlogOS re-audit (protocols/blog/blog-os-master.md STEP 7–8) plus these:
- No numbers/formulas in prose. Outside
```calcfences, no bare digits or$in paragraphs, list items, or headings (formula-as-inline-code allowed only in the answer blockquote / FAQ). - Every worked number lives in a
calcblock, captioned, and matches the site's calculator output (the calculator is the source of truth). - Key/value
calclines use=so they grid-align; results land after the final=. - Answer blockquote first; paired-calculator link only if the route exists.
- Few
##; bold lead-ins, not H3 soup;## Frequently asked questionswith###Qs. - Voice: fluff cut, no em-dash crutch, no semicolons, anti-slop clean.
-
dateModifiedbumped. - Featured image exists on disk.
public/blog/<slug>.webpis present (not just referenced in frontmatter) — sourced from a free Unsplash photo per §5 + the media protocol, with the pick recorded inscripts/unsplash-images.json. A post whosefeaturedImagepoints at a missing file is not shippable. This is part of writing/reviewing the post, never a separate follow-up step. Verify:ls public/blog/<slug>.webp(must exist) and that the slug is in the manifest.
Quick prose-number grep (run from repo root, swap the slug):
awk '/^```calc/{c=1} /^```$/{if(c){c=0; next}} !c' content/blog/<slug>.md \
| grep -nE '[0-9]|\$' | grep -v '^[0-9]*:#' || echo "clean: no numbers in prose"
Verify rendering on the dev server (localhost:3001), not a fresh prod build —
running npm run build against a live next dev corrupts .next. Type-check with
npx tsc --noEmit. Only run the prod build when the dev server is stopped.


