chapter one

A small mark, considered.

p9r is a numeronym for programmer — nine letters folded into a quieter shape. This is a workshop notebook for the practice of writing software the way one might shape a clay vessel: slowly, attentively, and without ornament that does no work.

What p9r is.

A small reference garden for honest software practice.

turn the card
verso

p9r.dev collects field notes, working snippets, and short essays on the daily work of writing programs. It does not sell anything. It does not measure attention. It is closer to a potter's logbook than a product page — entries are dated, materials are named, and mistakes are kept.

Updated when there is something true to say.

What it is not.

No analytics, no popovers, no consent labyrinth.

turn the card
verso

There is no newsletter capture. No referral funnel. No A/B framework deciding which sentence converts you. The site is one long page of plain text on warm linen, and the page is the entire offering.

If something is missing, it is missing on purpose.

A note on naming.

Numeronyms shorten without abbreviating — the count remains.

turn the card
verso

p, 9, r — the first letter, the silent middle, the last letter. The middle nine carry the weight of the word but cease to be pronounced. It is the same compression a craftsperson performs on a sentence: keep the bones, lose the description, trust the reader to fill the form.

fn numeronym(s: &str) -> String {
    let n = s.chars().count().saturating_sub(2);
    let first = s.chars().next().unwrap_or(' ');
    let last  = s.chars().last().unwrap_or(' ');
    // p + 9 + r
    format!("{first}{n}{last}")
}
chapter two

Tools should be warm to the touch.

The dark terminal is not the only honest surface for code. A program is also a paragraph, and a paragraph wants paper, light, and air. We can write software in rooms with windows.

Material honesty.

Show the grain. Show the seams. Show the work.

turn the card
verso

A wooden bowl reveals its lathe marks. A linen shirt reveals its weave. Code, similarly, can reveal the choices that made it — comments that explain why rather than what, type names that confess their domain, error messages written in the voice of the person who will read them at three in the morning.

The seam is not a flaw. It is the proof of construction.

Quietness as a feature.

A tool that does not interrupt is a tool you can think inside.

turn the card
verso

A good chisel does not chime when you put it on the bench. A good editor does not pulse for your attention. The most respectful thing a piece of software can do is recede, and become invisible in the moment of its use.

Notifications are a tax on concentration.

The brief that wrote itself.

A short list of working principles, phrased as instructions to the next me.

turn the card
verso — principles
  1. Pick a problem small enough to finish before forgetting why you started.
  2. Name the thing you are afraid to name. The naming is half the design.
  3. If the test is hard to write, the boundary is wrong, not the test.
  4. Delete more than you add. Twice as often as you think.
  5. Leave a note for the person who will read this in two years. That person is you.
chapter three

How a small thing is made.

The craft of programming, like the craft of pottery, is mostly preparation. Wedge the clay. Center the wheel. The shape comes last and quickly. Below: a few of the preparations that recur.

Begin with the smallest test.

A single line that fails for the right reason.

turn the card
verso — example

Before any implementation, write the test that proves the new behaviour does not yet exist.

// p9r/journal.test.ts
import { describe, it, expect } from "vitest";
import { entries } from "./journal";

describe("journal", () => {
  it("is empty on a fresh kiln", () => {
    expect(entries()).toEqual([]);
  });
});

Name domains, not types.

A type that means nothing is a type that means everything.

turn the card
verso — example

Replace anaemic primitives with names that carry their context.

type EntryId = `p9r-${string}`;
type PostedAt = { readonly iso: string };

interface Entry {
  id: EntryId;
  postedAt: PostedAt;
  title: string;
  body: string;
}

Write the comment to the future reader.

Explain why. The what is in the diff.

turn the card
verso — example
// We sort entries by postedAt rather than by id, because
// id is allocated when the draft is created, not when
// it is published. Two drafts written in the morning may
// publish in the evening in the opposite order; the reader
// sees publish-order, not authorship-order.
const ordered = entries.slice().sort((a, b) =>
  a.postedAt.iso < b.postedAt.iso ? -1 : 1
);

A diff tells you what changed. A comment tells you why anyone should care.

chapter four

A short shelf of finished things.

A workshop is judged by what leaves it. These are small, quiet pieces — libraries, tools, and notes — that have left the bench and now live on other people's desks.

linen-log

A tiny structured logger that prefers human-readable rows.

turn the card
verso — v0.4

Renders structured records as a single, scannable column rather than a wall of JSON. Reads beautifully on a paper terminal and aligns numerically by default.

$ linen-log tail
10:14:22  info   render   "page warmed in 62ms"
10:14:23  info   journal  entries=142
10:14:25  warn   cache    miss="index:home"
10:14:25  info   cache    refilled in 9ms

grain-css

Eight bytes of noise — a CSS-only grain texture for any surface.

turn the card
verso — v1.2

A single CSS file that adds tactile grain to any element via the .grain utility. No images, no JavaScript — just an inline SVG turbulence filter applied as a background.

.grain::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,…");
  opacity: 0.04;
  mix-blend-mode: multiply;
  pointer-events: none;
}

kiln

A single-binary static site oven. Markdown in, warm pages out.

turn the card
verso — v2.0

Walks a directory of .md files, applies a single template, and emits a static site with consistent typographic detailing. Designed for journals, not for landing pages. No client-side JavaScript by default.

$ kiln fire ./journal
  read   142 entries from ./journal
  render 142 pages, 1.8 MB total
  stage  ./out (warm)
  done in 312ms.
chapter five

Sit down at the bench.

There is no signup, no waitlist, no invitation code. The workshop has a door you can push open. What you make in here belongs to you. What you leave on the bench may be useful to the next person who comes through.

Read the journal.

Short, dated, sometimes wrong. Always finished.

turn the card
verso — latest
  • On naming a function before you can write it.
  • The two-screen rule for code review.
  • Why I stopped using any.
  • A morning with one chisel.

Send a letter.

A real one, with sentences. Replies are slow on purpose.

turn the card
verso — address

Write to bench@p9r.dev. There is no template, no character limit, and no autoresponder. Mail is read on Friday afternoons over a pot of hojicha.

Replies usually within a week. Sometimes two. Never within an hour — that would be insulting to the question.

Colophon.

The materials of this page, named.

turn the card
verso — colophon
Type
Cormorant Garamond, EB Garamond, IBM Plex Mono — all open licenses.
Surface
Warm linen #F5F0E6, cream clay #FAF7F0, fired umber #3B2F2F.
Accents
Raspberry, citrus, matcha, bluebell, apricot — never above 12% surface.
Texture
SVG turbulence noise at 3-5% opacity, blended multiply.
Made with
Plain HTML, plain CSS, one small JavaScript file.