Contract the output — keep changing the inside, even after you ship

Part 2 of 5 — series: Building a publishing tool, and shipping it. Last time I shared the overall picture and premises. Each part stands alone; see the series index. This time: keeping the inside changeable even after you ship.

A tool only you use, you can rebuild however you like. The trouble starts after you publish it and other people begin using it.

Rename a single config key, say, and anyone who wrote settings with the old key suddenly finds their setup broken. Once it’s out, you can’t casually change the parts people lean on. “Whatever is visible from the outside, someone will eventually rely on” — this is the everyday face of what’s known as Hyrum’s Law.

So before shipping, you decide one thing: what to fix as a promise, and what to leave free to change on your own terms. crofty puts that promise on the output.

Input or output?

Here “input” and “output” mean what the tool (crofty) takes in (the posts and settings you write) and what it produces (dist). The choice is which side to put the promise on.

Contract the input Contract the output
What’s fixed config keys, file layout, theme internals what must appear in the generated HTML
Changing internals tends to break users’ setups free, as long as the contract holds
What hurts renaming a key, reworking a part only breaking the contract itself

Contract the input and your internals become the promise; contract the output and the way you build is free. crofty chose the latter.

What the “contract” really is

Words alone stay abstract, so here’s the real thing. Every post page crofty generates has this in its <head>:

<!-- generated HTML (excerpt) -->
<html lang="en">
  <head>
    <title>Post title · Site name</title>
    <link rel="canonical" href="https://example.com/posts/hello/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- plus: a feed (/feed.xml) exists, and nothing phones home -->
  </head>

These “always present” items are the contract. crofty won’t emit a page missing its lang or canonical. Conversely, what’s not here — how the theme is built, how the HTML is assembled — is free to change.

crofty's internals are free to change; below them the output contract (the guarantees in dist) is fixed; below that the published site stays unbroken as long as the contract is the same — a three-layer diagram.
Only the middle line (the output contract) is fixed; the implementation above it can move.

Enforced by a machine, not a document

A promise written only in prose drifts from the implementation and rots. So crofty doctor checks, every time, that the built dist meets the contract.

$ crofty doctor
✓ output contract: all good

Checks: canonical link, feed, <html lang>/<title>/viewport, no phone-home.

Now the contract is a line a machine holds, not a best effort. However much you rework the internals, as long as doctor passes, users’ sites keep their promise.

You still can’t get rid of input entirely

Even with the output under contract, the input side — a post’s front matter, some settings — doesn’t vanish. What matters is drawing the line clearly.

Place How it’s handled
files crofty owns crofty writes them
your files (hugo.yaml, etc.) left alone — crofty only guides
visual customization a later-winning layer (custom.css from crofty theme eject) overrides

Even after you override, doctor still checks the contract, so “what you’re free to change” and “what’s held” never get mixed up.

It works anywhere

This isn’t special to crofty. It’s the general practice of defining a public API by its visible behavior, not its internals.

Choose the promised surface — the visible surface — deliberately small. What to freeze, and what to keep movable: the design of a tool you give away starts from drawing that line.


← Previous: Keeping what you write in your own hands | Next: Designing a multilingual site