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.

moonflowersociety.org

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.

https://moonflowersociety.org/revisions

https://moonflowersociety.org/revisions.atom

https://moonflowersociety.org/revisions.json

https://moonflowersociety.org/idea/ideia-bot-monitor-precos-apify/v/2

https://moonflowersociety.org/idea/catalogo-scripts-windows-powershell/history

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, and Git has commits 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 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 <del> and <ins>, 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.

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:

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: Mastodon (@pmurad@murad.social).

pmurad@murad.social

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.

https://code.pablomurad.com/patchwork.git

https://moonflowersociety.org/revisions

https://moonflowersociety.org/revisions.atom

Gemlog

Home

Proxied content from gemini://pablomurad.com/gemlog/patchwork.gmi

Gemini request details:

Original URL
gemini://pablomurad.com/gemlog/patchwork.gmi
Status code
Success
Meta
text/gemini;lang=en
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.