Patchwork
My site, moonflowersociety.org, is a nocturnal garden of ideas. It's 34 Markdown files about small-web projects, homelab guides, a price-watching bot and a Tamagotchi that talks through GPT-2 running inside DOSBox. None of them is finished. I go back, fix a number, add a paragraph, move a section around, change my mind.
RSS has no way to say any of that. A feed announces new posts, and that's all it was built to do. When I rewrite an old page I can stay quiet, and the people who read it last month never learn it changed. Or I can bump the date and pretend it's new, which is a small lie that also drops a page they've already read back into their reader.
Patchwork is my fix for this. It keeps every version of the pages on a site and publishes the revisions I choose to announce as feed entries. Each entry carries a one-line note from me, the kind of change, how grown the page is, and the passages that changed, with removed words struck out and added words marked.
Go look at it first #
The demo is live on moonflowersociety.org. The garden is written in Portuguese, but the machinery around it is in English, and a diff reads fine in any language.
- What changed, as a page: https://moonflowersociety.org/revisions
- The Atom feed: https://moonflowersociety.org/revisions.atom
- The same thing as JSON Feed: https://moonflowersociety.org/revisions.json
- One revision with its full diff: https://moonflowersociety.org/idea/ideia-bot-monitor-precos-apify/v/2
- One page's history: https://moonflowersociety.org/idea/catalogo-scripts-windows-powershell/history
- A feed for a single page: https://moonflowersociety.org/idea/ideia-bot-monitor-precos-apify/revisions.atom
And my favorite, from any terminal:
curl moonflowersociety.org/revisions
You get the revisions as text, with removed words in red and added words in green. Add ?plain if your terminal hates color.
The first three revisions went out on October 24, 2026, one of each kind. A correction put the missing accents back in a title. A reorganization moved two sections of the Tamagotchi idea so they read in the order the code runs. An expansion added two paragraphs to the price bot that spell out how its price limit treats shipping. They're small edits on purpose, because that's what most real revisions look like.
Back in the garden, the three flowers that changed wear a faint halo for two weeks. The price bot now grows as a sprout instead of a full bloom, because I marked it as one: an idea that's still taking shape should look like it.
Why bother #
Digital gardens have been around for years, and the people who keep them write a lot about tending notes. Almost nobody tells readers when a note changed. Wikipedia has page history, which is great for Wikipedia. Git has commits, which work for people who read diffs for a living. Blogs have nothing.
I wanted the middle ground. The author decides what's worth announcing and writes one sentence about it. The reader gets a readable diff in the feed reader they already use, without visiting the site to play spot-the-difference.
The pitch started in a chat with ChatGPT as a single paragraph: a feed of changes for living pages. I liked it enough to build it properly, and moonflower was the obvious first patient.
How it works #
You keep writing Markdown the way you already do. Patchwork watches the folder, and when a file changes it stores a snapshot in SQLite. Nothing reaches the feed until you add a note to the frontmatter:
---
title: Remote work
revision: "Two years in, I changed my mind about the hybrid model."
revision_kind: position # correction | expansion | position | reorganization
maturity: sprout # seed | sprout | bloom
---
The rules are few, and each one exists because I tripped over its absence:
- The first time Patchwork sees a file, it records version 1 and never announces it. Installing it on an existing site doesn't spam readers with 34 entries on day one.
- Edits without a new note stay as private history. You can fix typos all week and nobody gets pinged.
- A new note publishes a revision, diffed against the last published version. Five quiet edits followed by one with a note show up as one revision, the way a reader would want them.
- Changing only the note rewrites the last revision's note. No duplicate entry.
- Uploading again within 20 minutes folds the change into the revision you just published. That one is for the typo you always spot thirty seconds after hitting upload.
The kind I care about most is position. A page that now says something different from what it said last year should admit it out loud.
What a diff looks like #
Patchwork compares documents block by block first (paragraphs, list items, headings, code fences, tables), then word by word inside each block that changed. It notices when a paragraph moved, so you don't get a deletion here and an identical insertion there. Code blocks and tables stay whole instead of turning into word soup.
Here's the correction from the demo, straight from the command line:
$ patchwork diff -plain catalogo-scripts-windows-powershell
#1 → #2 "Acentos corrigidos no título e no resumo, e o nome do WinUtil igual em todo o texto." (+1 -0 words)
title: Catalogo de Scripts Windows PowerShell → Catálogo de Scripts Windows PowerShell
summary: Catalogo pratico de scripts e utilitarios para instalar, ajustar, limpar e reparar PCs com Windows. → Catálogo prático de scripts e utilitários para instalar, ajustar, limpar e reparar PCs com Windows.
… 48 unchanged
Ranking brutalmente honesto
~ Mais equilibrado: Chris Titus {+Tech +}WinUtil.
Mais completo: Sophia Script.
… 31 unchanged
In the feed, the same change uses and , which keep their meaning even in readers that strip every bit of CSS. Your site gets JSON instead, split into segments, and draws them in its own style:
{
"op": "modify",
"kind": "list_item",
"segments": [
{ "t": "eq", "s": "Mais equilibrado: Chris Titus " },
{ "t": "ins", "s": "Tech " },
{ "t": "eq", "s": "WinUtil." }
]
}
Patchwork never hands your site HTML to paste in.
How it's built #
Patchwork is one Go binary. SQLite is compiled in through modernc.org/sqlite, so there's no cgo, and the container is a 25 MB static binary on distroless, running as a non-root user. On the moonflower server it uses about 18 MB of RAM for 34 pages, under a 64 MB cap I set because I like my servers quiet.
I built it together with Claude Code: Claude wrote the Go while I steered, and every rule was tried on my own garden before it shipped. A few decisions took more thought than the rest.
- Dates. On the first scan, every page without a date in its frontmatter would have been dated to deploy day, which would make the whole garden look a week old. Patchwork takes the frontmatter date, then a date prefix in the filename, then the file's modification time when it first saw it, and it never moves that date again.
- Slugs. Moonflower builds its URLs in JavaScript, and Patchwork builds them in Go. A shared table of 30 nasty filenames, generated by the site's own code, keeps the two in agreement. That's how I found out that a filename made only of emoji produces an empty slug in both, and patchwork doctor now warns about it.
- One output, two delivery modes. Everything Patchwork publishes is built as a tree of files in memory. serve answers HTTP with ETags and 304s, and export writes the same bytes to a directory for static sites.
- Admin from the outside. retract and amend run as separate processes against the same database. Since 0.1.1 the server checks SQLite's data_version every three seconds and rebuilds only when another process has committed something.
On the site side, moonflower is an Astro app. It asks Patchwork for history with a 400 ms timeout and keeps the last good answer for up to a day, so if Patchwork falls over, the garden keeps working and simply stops showing history. The feeds go through the site's own domain, so readers never see Patchwork's address.
Try it on your own site #
You need Go 1.26:
git clone https://code.pablomurad.com/patchwork.git
cd patchwork
go build -o patchwork ./cmd/patchwork
If you'd rather run a container, the repository has a Dockerfile:
docker build -t patchwork .
Then point it at your content:
./patchwork init # writes a commented patchwork.yaml
./patchwork doctor # checks the config, the content and the database
./patchwork scan # records version 1 of every page
./patchwork serve # watches the folder and serves the API and the feeds
The parts of moonflower's config that matter most look like this:
site:
title: Moon Flower Society
base_url: https://moonflowersociety.org
tag_authority: "moonflowersociety.org,2026" # part of every entry id; pick once
page_url: "/idea/{slug}"
version_url: "/idea/{slug}/v/{n}"
history_url: "/idea/{slug}/history"
source:
mode: folder
path: /data/ideias
include: ["*.md", "*.markdown"]
Showing history on a page takes one request. This is the gist of what moonflower does:
const res = await fetch(`${PATCHWORK_URL}/api/v1/pages/${slug}`);
if (res.ok) {
const page = await res.json();
const latest = page.versions.find((v) => !v.baseline); // newest first
// latest?.note, latest?.kind, page.maturity, page.history_url
}
Run scan before you upload any edited page. Version 1 is whatever Patchwork sees first, so an edit made before that becomes part of the starting point instead of a revision.
What's next #
Folder mode is the only mode today. Next on the list:
- Git mode, reading history straight from a repository, so a site built from Git gets its whole past on day one.
- A CI action that runs export before the site builds, for static hosts.
- HTML output, for sites that want history pages without writing a single template.
- Renames. Right now renaming a file starts a new page with an empty history. Of all the current limits, that's the one that annoys me most.
The JSON contract is v1 and may still change before 1.0.
Come poke at it #
If you keep a garden, a wiki, documentation or any page you don't consider finished, try Patchwork and tell me what breaks: [contact: email or Mastodon handle].
The code is MIT-licensed at https://code.pablomurad.com/patchwork.git. The demo lives at https://moonflowersociety.org/revisions, and if you only want to watch a garden change, subscribe to https://moonflowersociety.org/revisions.atom.