devlog style guide

Detailed rules for writing, diagrams, code, and quality. The SKILL.md references this file — read it before writing any article.


1. Writing style

Voice

Hacker News peer-to-peer register. Imagine explaining to a smart engineer at your company who missed the meeting where this was decided. They know the stack, they know the domain, they don’t know this specific change.

Structure: the HN arc — a story, not a summary

The best technical posts tell a story. They follow this shape:

  1. State a concrete problem — not an abstract goal, a specific thing that was broken or missing. Open in the world before the fix, and make the reader feel the pain.
  2. Show why it was harder than it looked — false starts, constraints, the obvious solution that doesn’t work. This is the tension.
  3. Walk through the actual approach — the resolution, told as the payoff to that tension, not a feature list. This answers “what really changed?”
  4. Share honest learnings — what broke, what’s uncertain, what you’d do differently.

This arc works whether you’re explaining a 20-line bugfix or a system redesign. Scale the depth, not the structure.

The summary smell. If your sections could be shuffled into any order without loss, you wrote a changelog, not a story. Each beat should set up the next: the problem makes the reader want the resolution; the complication makes the resolution satisfying. Test it — try reordering your sections. If nothing breaks, the narrative chain isn’t there yet.

Paragraph discipline

  • Lead with the claim, then support. Don’t build up to the point — state it, then explain.
  • One idea per paragraph. If you’re making two points, that’s two paragraphs.
  • Cut throat-clearing. Delete opening phrases like “In order to…”, “It’s worth noting that…”, “As mentioned above…“. Start with the real sentence.
  • Concrete over abstract. “The cache evicts entries after 15 minutes” beats “The cache uses a time-based eviction strategy.”
  • Active voice default. “The handler validates the token” not “The token is validated by the handler.” Passive is fine when the actor is genuinely irrelevant.

Jargon calibration

Use technical terms when they carry meaning — don’t explain what a mutex is. But expand project-specific acronyms on first use. The reader knows general engineering; they may not know your project’s internal vocabulary.

Humor

A wry aside or honest self-deprecation is welcome. “We added a cache, which naturally led to two new bugs” is fine. Forced jokes, puns in headings, and emoji are not. The humor should come from the technical reality. If you have to try, skip it.

Length

Shorter is better. A good devlog respects the reader’s time.

Change sizeTarget word countDiagramsCode snippets
Small (bug fix, config change)400–7000–11–2
Medium (new feature, refactor)800–12001–22–4
Large (system redesign, new service)1200–18002–33–5
Architectural plan (no code yet)600–10001–20–2

These are guides, not rules. A 200-word devlog that nails the key insight is better than a 1500-word one that covers everything.


2. Headlines and section titles

Every heading is an assertion, not a label.

BadGood
Authentication changesJWT tokens now rotate on every refresh
Database migrationUser table splits into profiles and credentials
Performance improvementCold starts dropped from 4.2s to 800ms by lazy-loading the ORM
ArchitectureThe queue now sits between the API and the workers

Section headings (h2) follow the same rule. h3 sub-headings can be shorter labels when they’re structuring a list of related points within a section.


3. Code snippets

Inclusion criteria

A code snippet earns its place only if:

  • It shows a non-obvious decision — an algorithm choice, an API design, an error-handling strategy
  • It would take more prose to explain without the code than with it
  • The reader needs to see the shape of the code to understand the trade-off

Boilerplate, imports, type declarations, and obvious getters stay out. If the function is 40 lines but only 8 matter, show the 8 and elide the rest with // ....

Formatting

  • 10 meaningful lines max per snippet
  • File path + function name as a label above the block: src/cache/lru.ts → evict()
  • 2-3 sentences of explanation immediately after each snippet, answering: what does this do, and why was it done this way?
  • HTML entities for < (&lt;), > (&gt;), & (&amp;) inside <code>
  • No syntax highlighting via JS — use CSS classes if you want minimal color, or keep it plain. The file must work without any scripts.

Prose-code relationship

Write the prose so the article reads coherently even if all code blocks were removed. The prose explains; the code illustrates. Never write “as shown above” as the sole explanation — state the point in words, then point to the code for specifics.


4. Diagrams

Show over tell — and reinforce freely

Write for visual thinkers: when a change has a shape — hierarchy, direction, sequence, spatial relationships, before/after — draw it. Default to showing, and give every major idea a visual anchor the eye can land on.

A diagram does not have to replace the prose. A visual that simply re-draws what a paragraph just said is welcome — the reader takes in the picture and the words as one pass, and the redundancy reinforces the point. Pair prose with a diagram freely; you don’t have to choose between them.

The bar is only this: the diagram must be true and legible — an accurate picture of the real change, readable at a glance. Cut diagrams that misrepresent the actual topology or are pure decoration with no content. Reinforcing the prose is good; misleading or empty is not.

SVG rules

All diagrams are inline SVG. No Mermaid rendering, no JS, no external images, no <img> tags.

Canvas and palette:

Background:     #fafaf8  (matches page --bg)
Node fill:      #f5f5f0  (light warm gray)
Node stroke:    #1a1a1a  (near-black, 1px)
Focal fill:     #fff7ed  (warm accent tint)
Focal stroke:   #e8590c  (accent orange, 1.5px)
Arrow stroke:   #888     (tertiary gray, 1.2px)
Focal arrow:    #e8590c  (accent, 1.4px)
Label text:     #1a1a1a  (primary) or #555 (secondary)
Mono labels:    12px monospace, letter-spacing 0.05em
Name labels:    14px system sans, weight 500

Layout:

  • viewBox based, no fixed width/height on the <svg> element — let it scale
  • Typical viewBox: 0 0 700 340 (landscape), 0 0 500 500 (square for state machines)
  • All coordinates divisible by 4
  • Node sizes from a fixed set: width 120/140/160, height 40/56
  • Arrow endpoints land exactly on node edges, not “near” them
  • Arrowheads: open chevron paths (M2 1 L8 5 L2 9), not filled triangles
  • Leave generous whitespace — cramped diagrams are worse than no diagram

Complexity budget:

  • Max 9 nodes per diagram. If you need more, split into two diagrams.
  • 1-2 focal elements per diagram (accent color). Everything else neutral.
  • If two nodes always appear together, merge them into one.
  • If an arrow’s meaning is obvious from layout alone, remove the label.

Diagram types

Architecture diagram — boxes and arrows showing components and connections:

┌─────────┐     ┌─────────┐     ┌─────────┐
│ Client  │────▶│   API   │────▶│  Cache  │
└─────────┘     └────┬────┘     └─────────┘
                     │
                     ▼
                ┌─────────┐
                │   DB    │
                └─────────┘

Use when showing how components relate. Highlight the new/changed component as focal.

Sequence diagram — vertical lifelines with horizontal arrows:

  Client          API          Auth          DB
    │              │             │            │
    │──request──▶  │             │            │
    │              │──validate─▶ │            │
    │              │  ◀──token── │            │
    │              │──────────────────save──▶ │
    │  ◀──response─│             │            │

Use for request flows, event ordering, async interactions. Keep to 4-5 participants max.

Flow diagram — left-to-right or top-to-bottom pipeline: Use for data transformation pipelines, build processes, deployment flows.

State machine — circles/boxes with labeled transition arrows: Use for lifecycle states (draft→published→archived), connection states, process stages.

Before/after — two small diagrams side by side: Use when the change restructures something and the reader needs to see both states to understand the difference.

Diagram anti-patterns

PatternWhy it fails
Every node in accent colorNothing is focal — the accent means nothing
Diagram misrepresents the real topologyWorse than no diagram — it teaches the wrong mental model
Arrow labels without background rectsLine bleeds through text, becomes unreadable
Gradient fills or drop shadowsLooks “designed” instead of informational
More than 9 nodesToo dense to scan — split into multiple diagrams
Decorative icons in nodesNoise. Use text labels.
Exact same info as a nearby code snippetPick one: diagram or code, not both for the same point

5. The scrutiny section

This is the most important section for review purposes. It answers: “What should I actually look at before approving this?”

What to flag

CategoryExample
Architectural fit”This adds a second caching layer — do we want two?”
New abstractions”The EventBus class is new — currently only used in one place”
Convention breaks”Other handlers use middleware for auth; this one checks inline”
Wheel-reinvention”This custom retry logic is similar to what @utils/retry already does”
Agent uncertainty”The token expiry value (15min) was guessed — worth verifying”
Missing tests”The error path in rotateToken() has no test coverage”
Naming drift”The function is called processData but it only validates emails”

How to flag

Be specific, not vague. Every scrutiny point should name:

  1. What — the specific function, file, pattern, or decision
  2. Why it matters — the concrete risk if this is wrong
  3. What to check — what the reviewer should verify

Bad: “There may be performance implications.” Good: “The findRelated() query joins four tables without an index on created_at. With the current 50K rows this is fine, but at 500K it’ll table-scan. Worth adding the index now or tracking as a follow-up.”


6. Anti-slop patterns

Common AI writing failures adapted for devlog context. Check against these before finalizing.

#PatternSymptomFix
1Adjective pile-up”Implemented a robust, scalable, and efficient caching solution”Name what it actually does: “Added an LRU cache in front of the token store”
2Throat-clearing opener”In order to improve the authentication flow…”Delete. Start with: “The auth flow now rotates JWT tokens on every refresh.”
3Label headingsSection titled “Changes” or “Implementation”Assertion: “Token rotation replaces static session keys”
4Restating the headingHeading says “Cache invalidation”, first sentence says “We implemented cache invalidation”The body adds information the heading doesn’t carry
5Vague scrutiny”There may be edge cases to consider”Name the edge case: “If two requests hit rotateToken() concurrently, the second one gets a revoked-token error”
6Invented precision”Reduces latency by approximately 47%“Only state metrics from actual measurement. If unmeasured: “Should reduce latency — not yet benchmarked”
7Comprehensive but unclearCovering every file touched instead of the key decisionsCut to the 3-5 decisions that matter. Link to the full diff for the rest.
8AI corporate speak”Leveraging the new architecture to unlock performance gains""The new architecture is faster because queries hit the cache first”
9Empty diagramA diagram with no real content — generic “Client → Server → Database” that isn’t the actual topologyDraw the real shape of this change. Re-visualizing the prose is fine; drawing a placeholder that says nothing is not.
10Explaining the obviousParagraph explaining what REST is, or how HTTP status codes workThe reader is an engineer on the project. Skip it.

7. HTML/CSS reference

Full CSS template

Use this as the base stylesheet. Adjust only content-specific elements (like the number of SVG nodes), never the typographic scale or color tokens.

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
 
:root {
  --bg: #fafaf8;
  --text: #1a1a1a;
  --text-2: #555;
  --text-3: #888;
  --accent: #e8590c;
  --accent-bg: #fff7ed;
  --border: #e5e5e0;
  --code-bg: #f5f5f0;
  --mono: 'SF Mono', 'JetBrains Mono', Consolas, 'Liberation Mono', monospace;
  --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --serif: Charter, Georgia, 'Times New Roman', serif;
}
 
body {
  font-family: var(--serif);
  font-size: 17px;
  line-height: 1.7;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
 
article {
  max-width: 720px;
  margin: 0 auto;
  padding: 3rem 1.5rem 5rem;
}
 
/* --- Header --- */
.eyebrow {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.5rem;
}
 
h1 {
  font-family: var(--sans);
  font-size: 1.85rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--text);
  margin-bottom: 0.75rem;
}
 
.lede {
  font-size: 1.1rem;
  color: var(--text-2);
  line-height: 1.6;
  margin-bottom: 2.5rem;
  padding-bottom: 2rem;
  border-bottom: 1px solid var(--border);
}
 
/* --- Sections --- */
h2 {
  font-family: var(--sans);
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--text);
  margin-top: 2.5rem;
  margin-bottom: 0.75rem;
}
 
h3 {
  font-family: var(--sans);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text);
  margin-top: 1.8rem;
  margin-bottom: 0.5rem;
}
 
p { margin-bottom: 1rem; }
 
/* --- Code blocks --- */
.code-block {
  margin: 1.5rem 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
 
.code-label {
  display: block;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--text-3);
  background: var(--border);
  padding: 0.35rem 0.75rem;
  letter-spacing: 0.02em;
}
 
pre {
  background: var(--code-bg);
  padding: 1rem;
  overflow-x: auto;
  margin: 0;
}
 
code {
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1.55;
  tab-size: 2;
}
 
/* Inline code */
p code, li code {
  font-size: 0.85em;
  background: var(--code-bg);
  padding: 0.15em 0.35em;
  border-radius: 3px;
  border: 1px solid var(--border);
}
 
/* --- Diagrams --- */
figure {
  margin: 2rem 0;
}
 
figure svg {
  width: 100%;
  display: block;
}
 
figcaption {
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.88rem;
  color: var(--text-2);
  margin-top: 0.6rem;
  line-height: 1.5;
}
 
/* --- Scrutiny section --- */
.scrutiny {
  border-left: 3px solid var(--accent);
  padding-left: 1.25rem;
  margin: 2rem 0;
}
 
.scrutiny h2 {
  color: var(--accent);
}
 
/* --- Lists --- */
ul, ol {
  padding-left: 1.5rem;
  margin-bottom: 1rem;
}
 
li {
  margin-bottom: 0.4rem;
}
 
/* --- Misc --- */
a {
  color: var(--accent);
  text-decoration: none;
}
 
a:hover {
  text-decoration: underline;
}
 
hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 2.5rem 0;
}
 
/* --- Responsive --- */
@media (max-width: 640px) {
  article { padding: 2rem 1rem 3rem; }
  h1 { font-size: 1.5rem; }
  .code-block { margin-left: -1rem; margin-right: -1rem; border-radius: 0; border-left: none; border-right: none; }
}

Code snippet HTML pattern

<div class="code-block">
  <span class="code-label">path/to/file.ts → functionName()</span>
  <pre><code>// key code here, max ~10 lines
// use &lt; &gt; &amp; for HTML entities</code></pre>
</div>
<p>Explanation: what this code does and why it was done this way. 2-3 sentences.</p>

Diagram HTML pattern

<figure>
  <svg viewBox="0 0 700 340" xmlns="http://www.w3.org/2000/svg">
    <!-- background -->
    <rect width="100%" height="100%" fill="#fafaf8"/>
 
    <!-- nodes, arrows, labels -->
  </svg>
  <figcaption>What this diagram shows, with the focal element named.</figcaption>
</figure>

Scrutiny section HTML pattern

<section class="scrutiny">
  <h2>What to scrutinize</h2>
  <p>Point 1: specific thing to check, why it matters, and what to verify.</p>
  <p>Point 2: ...</p>
</section>