An open notebook and fountain pen under an amber desk lamp, with a paper-finishing machine behind rain-speckled glass.

In May, I replaced Jekyll with plain HTML, CSS, and a few Web Components.

Part of the reason is I was moving from trying to monetize referral link advertising to just writing my thoughts and NOT over engineering something. I wanted the freedom and flexibility and the ability to add things quickly.

I kept Markdown for writing and S3 behind Cloudflare for hosting. Over the following months, publishing moved into Swamp models and workflows.

Jekyll already handled Markdown. What I wanted was a clearer separation between working on an article and putting it on the web.

Because a post needs more than HTML. The homepage, writing archive, and RSS feed need updating. The CDN needs fresh files. Old links still need to work.

The draft stays in Obsidian

Obsidian is still my preferred markdown editor and reading environment. Its still the best editor in Android and just renders it's beautiful preview view that is combined in one screen as the editor. So elegant. I wish something like this existed for HTML directly...

My drafts live alongside the notes that feed them. Each is a Markdown file with a title, date, slug, and excerpt in its frontmatter.

I can rearrange an argument or revise it with an agent without creating a public page. The note lives outside the site repository; a draft label or an unlinked page would not, by itself, keep deployed content private.

Format conversion is not writing

Writing this post exposed a gap. The original blog-post create method accepted HTML. An agent was converting the Markdown before handing it to Swamp. The template and listings were deterministic; the conversion was not.

I want help revising a paragraph, not another revision disguised as a format conversion.

So we added preview and write methods to the existing blog-post model. They use the already-installed Python-Markdown and PyYAML libraries, with explicit version checks and no new JavaScript packages. Another machine still needs the matching parser environment.

Only the body between explicit start and end markers renders. Draft warnings and private notes stay outside. Raw HTML is escaped; unsafe links and unresolved Obsidian links or embeds stop conversion.

From the blog repository, I preview the note with the real site template:

swamp model method run blog-post previewMarkdown \
  --input sourcePath="/path/to/approved-article.md"

The preview is excluded from deployment and leaves posts and listings untouched. After reviewing it, I pass its render fingerprint to the write method:

swamp model method run blog-post writeMarkdown \
  --input sourcePath="/path/to/approved-article.md" \
  --input operation=create \
  --input expectedRenderHash="<renderHash from the reviewed preview>"

If the note or existing page changed, the write fails and requires a new preview. The fingerprint detects changes; it does not prove human approval.

Writing creates the local page, updates the homepage and writing archive, and regenerates RSS. For later revisions, I edit the Markdown and repeat with operation=update, preserving the cover. Nothing is live yet.

I've since packaged the reusable part as @mgreten/markdown-blog, an MIT-licensed Swamp extension for deterministic previews, local writes, and verification. Bring your own template and deployment; the README covers installation and requirements.

Publish, then verify

Optional artwork gets its own review. A Swamp workflow generates a candidate through Amp and Painter. After approval, another attaches it and adds typography. I inspect the finished cover too.

Publication is a separate decision. The deploy workflow requests a Git commit and push, uploads changed files to S3, removes stale objects, and purges Cloudflare after a successful upload. Then it checks the live site:

  • Browser checks open the homepage and a representative post, check the shared header and footer, and collect page errors.
  • URL checks combine discovered links with a retained baseline. A link can keep being checked after it falls off the homepage.

Results are saved before a failed check stops the workflow. There is a report to inspect, not just an agent saying the upload looked good.

These checks run after publication. They do not review every article or automatically roll back a broken deployment. I also need a current, reviewed checkout: the deployer can remove remote files missing locally.

Keep the judgment; automate the bookkeeping

This is a small example of wanting more from AI than another agent. An agent helps research and revise. Code converts the Markdown and updates the site. A workflow orders publication and checks. I decide when the article is ready.

There is still custom code and a parser environment to maintain. Jekyll remains a reasonable alternative. For this blog, I prefer having the repeated steps named, testable, and separate from the writing.

I want the agent's help with the next paragraph, not another improvised way to publish the last one.