Text

Everything that's wrong, at once

Every abort in this engine is correct where it stands. Each one also reports exactly one problem — the first one, from wherever in the code it happened to be noticed. Fix it, run again, meet the next one. Repeat until the thing builds.

./blog.sh doctor reads what is on disk and tells you the lot in one pass, in whole sentences, each with a fix line written for somebody who does not know which file that setting lives in.

It goes after the silent failures

A loud error at least announces itself. The problems doctor concentrates on are the ones that never say anything:

Číst dál

Walking the archive

A post here recently argued that the wizard menu got shorter because operations on a post belong in the post, not in a menu. That still holds. This release adds a screen anyway, and the reason is worth stating: finding a post is a different job from doing something to one.

./blog.sh browse shows the same posts list does, as a screen you stay in.

What it is

Arrows and Page Up/Down through the whole archive. Filters by type, state and tag, with the count next to each. And a search that filters as you type.

The search is the site's own. Words are ANDed, "a quoted phrase" counts as one, -word excludes, and diacritics never decide a match. The query language and the folding live in lib/search_query.rb, next to a note that it and assets/js/search.js change together — because a query that means one thing in your browser and another in your terminal is worse than having no search in the terminal at all.

It searches the full text of every post, so under the selected row there is a line of that post's own text showing you why it matched.

Číst dál

Most of your Facebook isn't yours

The Facebook importer was built against a real "Download Your Information" export. The first useful thing it told me was a number I did not expect.

On that export, 95 % of the posts were not written on Facebook. They were mirrored in — from Twitter, from Posterous, from the whole crosspost era when every service politely offered to repost you everywhere else.

They are skipped, and counted

Those posts do not import. Not because they are worthless, but because the platforms they actually came from have their own exports, and those carry the originals: the full text, the real timestamps, the media at the size it was uploaded. A Facebook mirror of a tweet is a worse copy of something you can import properly from the Twitter archive sitting in the same folder.

Import both and your archive doubles, with the lesser version winning half the time. So they are recognized and skipped with a count in the summary, and FACEBOOK_CROSSPOSTS=1 overrides it if you disagree. Wordless check-ins and app stories are counted skips too.

Číst dál

A palette you can look at first

An earlier post here explains that every colour on this site comes from seven keys in a config file. That part hasn't changed. What has changed is that you no longer have to imagine what those seven values will look like before you commit to them.

Choosing between fourteen hex values is exactly as blind in a wizard as it is in YAML. So ./style.sh shows you.

Seven palettes, one keystroke

Whole palettes now ship in config/palettes.yml, light and dark both:

  • Default blue, warm, monochrome and high contrast — the four from the gallery on this site, whose light modes are exactly what that page showed. Their dark modes are new; the gallery only ever had light homepages.
  • Sunflower, cream and gold with olive links — the golden yellow stays on the tag pills, because yellow text on cream cannot be read and the accent here is above all a text colour.
  • Garden, greens and khaki.
  • Ocean, steel blue over navy.
Číst dál

Rescuing a blog that died in 2013

Every importer here reads an export file. That works right up until the platform is gone — and then there is no export to ask for, no admin to log into, and no account to recover. Posterous shut down in 2013. Blog.cz is gone. Your host is next, eventually.

For those blogs the twentieth import source is the Internet Archive.

Feed mode: what the crawler kept

The Archive did something useful without meaning to. For years it crawled blogs' RSS feeds — over and over, every few weeks, whenever a crawler happened past. Each capture is a snapshot of the last ten or twenty posts at that moment. Line them up oldest first and the overlaps reconstruct a history nobody saved.

That is what the importer does: reads the CDX index for the feed's address, deduplicates by digest so a hundred identical captures cost one fetch, pulls the original bytes through the id_ endpoint so the Archive's own toolbar never lands in your posts, and merges the overlapping windows the same way a re-import merges anything else.

Images come back through the same time machine.

Číst dál

Your camera writes down where you were

Take a photo on a phone and it records the spot you stood on. Not as a guess — as coordinates, accurate to a few metres, in a block of metadata that travels with the file wherever it goes.

Social networks strip that on upload. They have done it for years, quietly enough that most people have stopped thinking about it. A static site has nobody to do it: blog.sh copied your photo into the archive byte for byte, and nothing in the engine had ever looked at metadata at all. So a picture of a cat on a windowsill published the windowsill.

From 1.2.1 it doesn't. A photo loses its location on the way into the archive — when you add it to a post, and equally when it arrives through any of the twenty-two importers, because they all end up in the same write.

Only the location

The camera, the lens, the exposure, the moment the shutter opened: those stay. They are your own record of your own photograph, and an archive that silently threw them away would be answering a question nobody asked.

Číst dál

Setting up is a conversation now

Until this release, installing ./blog.sh meant copying two files and editing 277 lines of commented YAML before the first build would run. Everything was documented. That is not the same as being easy.

./setup.sh asks instead, and checks every answer as it arrives.

The questions that were quietly dangerous

Three settings could be typed wrong in a way nothing would ever tell you about.

The timezone. A typo here does not fail — Ruby falls back to UTC and says nothing — it just dates every post two hours off, forever. The wizard offers the zone from the machine's own database, unless the machine sits on UTC, which is a fact about a datacenter rather than about the person answering; then it suggests from the language you are being asked in. And it refuses anything that isn't a real zone.

Číst dál

A blog engine that became an archive

The first post on this site is about a photo I put on Instagram in 2012. The link still works. The photo doesn't.

That post was about one archive — mine — and one rule: media comes home. This one is about what happened when that rule was applied to every platform I could find an export for.

The roster is closed

Twenty-two sources import now, in three groups.

Social networks: Bluesky, Facebook, Instagram, Mastodon, Pixelfed, Threads, Twitter/X.

Blogs and publishing platforms: beehiiv, Blogger, Ghost, Jekyll/Hugo and any markdown folder, LiveJournal, Medium, Movable Type and TypePad, podcasts (any feed with audio), Squarespace, Substack, Tumblr, Wix, WordPress and any RSS or Atom feed.

And dead sites: the Wayback Machine, for blogs whose platform no longer exists at all.

Číst dál

The guard that switched itself off

This site's deploy has two guards. If a build suddenly has far fewer files than what's live, or far fewer bytes, the deploy stops. They exist because a broken build looks exactly like a deliberate one to rsync --delete, and a static site is only ever one confident sync away from being erased.

In 1.0 they could turn themselves off. Permanently. Silently. Here is how, because the shape of this mistake is more useful than the fix.

The reference was the wrong thing

The guards compared the new build against the deploy manifest — a record of what's on the target. Reasonable, until an upload fails. A failed upload leaves the manifest out of true, and a guard measuring against a record it knows is wrong would fire on every subsequent run.

So there was a marker: after a failed run, stand the guards down until a clean run comes along and restores the reference.

Číst dál

The menu got shorter

The wizard used to list ten things you could do. It lists five now, and the engine can do more than it could before.

That's not a paradox, it's an admission: eight of those ten were not activities. They were operations on a post, wearing the costume of a menu item.

Everything about a post, in one place

./blog.sh props <slug> — or, in the wizard, pick a post and press v — shows what there is to know about it. State, type, tags, whether it's pinned, whether it's been announced and where. If it's scheduled, the whole publishing queue.

And the actions live there too, next to the facts they act on. A draft offers publish and schedule. A published post offers unpublish, re-announce, pin or unpin, rename, delete.

You no longer go to a menu, choose "unpublish", and then hunt for the post. You go to the post and see that unpublishing is one of the things it can do right now.

What stayed where it was

Číst dál