code.pablomurad.com

patchwork

Publishes the revisions of your pages as a feed, with notes and readable diffs, written in Go

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

Recent commits

  1. 744248a
    Show retract, amend and snapshot on a running serve without a restart
    
    serve rebuilt its output only after a scan found a changed file, so
    edits made by another patchwork process (retract, amend, snapshot)
    waited for the next upload or a restart. serve now polls SQLite's
    data_version every 3 seconds; it moves only when another connection
    commits, so serve's own writes don't trigger it and an idle site
    doesn't rebuild. Rebuilds are serialized.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    
  2. c893eb1
    Add a changelog for 0.1.0
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    
  3. 60e53a1
    Date undated pages by their first mtime, space out word swaps, add a Dockerfile
    
    Found while wiring Patchwork into moonflowersociety.org:
    
    - a page with no date in its name or frontmatter took the day of the
      first scan as its original date, so starting Patchwork on an existing
      site dated every such page to the deploy; the baseline now uses the
      file's modification time and keeps it on later uploads
    - removed words right before inserted ones ran together in feed readers
      and in colored terminal output ("modificationthe day"); they're now
      separated by a space
    - Dockerfile: static binary on distroless nonroot, with a data directory
      a fresh volume can inherit
    - .gitattributes: LF everywhere, so golden files match on Windows too
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    

Full log →

Readme

Patchwork

Your RSS feed tells readers what you published. Patchwork tells them what you changed.

Patchwork keeps the version history of the pages you revise and publishes each revision you choose to announce as a feed entry. An entry carries your note, the kind of change, how grown the page is, and a readable diff of the passages that changed. It’s meant for digital gardens, living notes and documentation: pages that keep growing after their first publication.

Status: early development. Folder mode, the feeds and the HTTP API work; git mode and a CI action are next. The v1 JSON contract may still change before the first release.

How it works

Patchwork watches a folder of Markdown files. When a file changes, it stores a snapshot in SQLite. A snapshot becomes a public revision only when you say so, by adding 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, in short:

  • The first time Patchwork sees a file, it records version 1. That version is never announced.
  • Edits without a new note are kept as history and never reach the feed, so fixing a typo makes no noise.
  • A new note publishes a revision, diffed against the last one you published. Several quiet edits followed by one with a note show up as a single revision.
  • Changing only the note rewrites the last revision’s note instead of adding an entry.
  • An upload with the same note within 20 minutes of publishing is folded into that revision (the typo you spot right after uploading).
  • A page leaves the feeds when its file is missing on two scans in a row.

Mark a block that changes on its own, like a “last checked” line, so it never counts as a change:

<!-- patchwork:ignore -->
Last checked: 2026-10-20
<!-- /patchwork:ignore -->

Install

go install git.pablomurad.com/pablo/patchwork/cmd/patchwork@latest

Patchwork is one binary with no cgo. SQLite is compiled in.

Quick start

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 feeds

Keep the database on a local disk. SQLite’s locking isn’t safe on network shares, which is why the default path is in your user cache directory.

Commands

Command What it does
init Write a commented patchwork.yaml.
scan [-dry-run] Scan the folder once and apply the publish rules. -dry-run shows what would happen.
watch Keep scanning as files change (file events, plus polling).
serve watch, and serve the API and feeds over HTTP.
export -out DIR Write the API and feeds as static files.
log <page> A page’s public history.
diff <page> [n \| a b] What changed in a revision, in color.
retract [-undo] <page> <n> Hide a published revision from the feeds and the API.
amend -note TEXT <page> <n> Rewrite a published revision’s note or kind.
snapshot -path P -file F -at T Import an older copy of a page, oldest first, to build history from before Patchwork.
rebuild-diffs [-force] Recompute cached diffs.
backup -to FILE Copy the database.
doctor Check everything without changing anything.
health Exit 0 if a running serve answers; for container health checks.

<page> is a slug or a path inside the content folder.

What it publishes

serve and export publish the same files:

Path Content
/api/v1/pages Every page, with its maturity and revision count.
/api/v1/pages/{slug} One page and its public history.
/api/v1/pages/{slug}/versions/{n} One version: its Markdown body and its full diff against the previous version.
/api/v1/revisions Recent revisions across the site.
/feeds/revisions.atom, /feeds/revisions.json Atom and JSON Feed of revisions.
/feeds/pages/{slug}.atom, .json The same, for one page.
/text/revisions, /text/pages/{slug}/history, /text/pages/{slug}/versions/{n} Plain text for terminals, colored by default; ?plain and ?w=100 adjust it.

API paths also answer with a .json suffix. Responses carry an ETag and answer conditional requests with 304. The JSON shapes are described by the JSON Schema files in schema/v1.

Diffs come as structured segments (eq, del, ins) for your site to render in its own style. Feed entries use <del> and <ins>, which keep their meaning in feed readers that strip CSS.

Configure site.base_url and the public feed paths in patchwork.yaml, so feeds link to your site and not to Patchwork’s own address. Pick site.tag_authority once: it’s part of every feed entry id, and changing it later makes readers see every entry as new.

First site

moonflowersociety.org, a nocturnal garden of ideas.

License

MIT. See LICENSE.