post systems

Post

A small publishing system that can last

A personal archive should be easier to leave than to maintain. This one starts with Markdown, Git, and a database we are always willing to throw away.

§Begin with the thing worth keeping

Publishing software invites a familiar reversal: the durable writing becomes trapped inside the most temporary part of the system. An editor changes direction, a database reaches the end of its supported life, or a hosting company disappears. The pages are still visible, perhaps, but the source has become an export job.

Fieldnotes makes a smaller bet. Each entry is an ordinary text file with a short YAML header. Directories supply topics; filenames supply stable slugs; Git remembers the edits. The web database is useful, but it is not precious. If it vanishes, one command reconstructs it from the files.

A diagram showing Markdown flowing through a local build into SQLite and then a readable website.

The distinction is architectural, but it also changes the writing habit. A note can begin in any text editor. It can be searched with familiar command-line tools, copied without an export format, and understood by someone who has never seen the site.

§Build offline, on purpose

The renderer has no reason to call a remote service. Python-Markdown turns the body into HTML, Pygments colours code locally, and SQLite stores the result beside the original source. The build begins with an empty temporary database and atomically replaces the old artifact only after every entry succeeds.

That rule makes failure pleasantly boring. A malformed date cannot leave half an archive behind, and two builds from the same tree produce the same database dump. A simplified version of the loop looks like this:

from pathlib import Path

def discover_entries(root: Path) -> list[Path]:
    """Return content in a stable order before rendering."""
    return sorted(root.glob("**/*.md"), key=lambda path: path.as_posix())

for source in discover_entries(Path("content")):
    entry = parse_entry(source)
    if not entry.draft:
        write_row(render(entry))

Black-on-white line art showing sorted source files entering a fresh database build before an atomic replacement.

The sort is modest but important. Reproducibility is usually the accumulated result of such modest decisions: explicit timestamps, canonical JSON, a known schema, and no dependence on whatever a network service happens to return today.

§Keep each layer replaceable

There are only three layers in the reading path, and each has a different expected lifetime.

Layer What it owns Replacement test
Markdown in Git Words, metadata, history Opens in a plain text editor
Build script Rendering and the search index Rebuilds from a clean checkout
Datasette site Routes and reading experience Can be replaced without editing content

SQLite is especially well suited to the middle of this arrangement. It is a single, inspectable file with excellent full-text search, yet it does not ask to become the source of truth. Datasette then turns that file into both a website and a useful JSON interface. Neither component needs write access in public.

This separation also gives the design room to have a point of view. The stored HTML can be set like a literary journal today and rendered by some future tool tomorrow. Presentation is taken seriously without being confused for preservation.

§Let Git carry time

Dates sound simple until an archive outlives its first migration. Filesystem modification times are fragile: copying a directory can rewrite them all. Git history is a better record, so the first commit supplies the creation date and the latest commit supplies the update date. Frontmatter remains an explicit override for imported or deliberately backdated work.

There is one hard edge worth keeping: if neither frontmatter nor Git can supply a creation date, the build stops loudly. Publishing an entry with invented time would make the archive look complete while quietly corrupting its chronology.

§Make the public surface smaller

The website is read-only. It exposes clean entry pages, topic indexes, feeds, full-text search, and Datasette’s JSON views, but arbitrary SQL is disabled. Authoring belongs on the other side of the Git boundary, where a person—or later, a tightly scoped publishing tool—creates a Markdown file and commits it.

That is the whole system: text files as the heirloom, a deterministic database as the catalogue, and a deliberately quiet website as the reading room. Small enough to understand is not a concession here. It is the feature most likely to keep the work readable in twenty years.