Tagauthoring

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.

Read more

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.

Read more

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.

Read more

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

Read more

The photo your iPhone won't show anyone

An iPhone shoots HEIC by default. Safari renders it. Chrome doesn't. Firefox doesn't. So a photo straight off the phone, attached to a post and published, is a photo most of your readers will never see.

The old behaviour was the worst possible one: the file went in, the build couldn't measure it, and the image quietly vanished from the page — taking its caption with it. No error. Just a post with a hole where a photo was.

Refused, with the command you need

Attaching a HEIC now stops the save and prints the exact conversion command for the machine you're standing at:

sips -s format jpeg photo.heic --out photo.jpg      # macOS
heif-convert photo.heic photo.jpg                    # Linux

Your file stays exactly where it was, in incoming/. Nothing is deleted, nothing is guessed at. You convert it and carry on.

Or converted, if you ask

Read more

Three drafts, three mornings

Writing doesn't arrive evenly. You get an evening where three posts want out, then nothing for a week. Publishing them the way they were written means three posts in one night and silence afterwards — which is a worse blog than the same three posts spread across three mornings.

So a site can now say when it usually publishes:

publishing:
  slots:
    - "mon 09:30"
    - "wed 09:30"
    - "fri 09:30"

Or, if you write more than that, a single "daily 09:00".

With that in the config, scheduling a draft offers you the next slot no other scheduled post already occupies. Three drafts written in one evening are offered Monday, Wednesday and Friday, in that order, without you counting days.

It only ever offers

This is the part I care about most, because a queue that takes decisions away from you is a queue you fight.

Read more

Some posts are downloads

A photo in a post is a bare filename in an image line. It always has been:

![A view from the window](window.jpg)

So a file attached to a post is the same line without the exclamation mark:

[Reading notes, 2025](reading-notes.pdf)

A whole line that is nothing but a link, pointing at a bare filename with a known extension, is an attachment. It gets staged through incoming/ like a photo, stored next to the post like a photo, and it never leaves your site.

A line with a URL in it stays what it has always been: a link. The difference is the bare filename — the same rule photos already follow, so there's nothing new to learn.

Rendered as a card, not a link

An attachment renders as a card carrying the label, the extension and the size. The size is there because a download deserves to say what it costs before you commit to it — a 40 MB PDF on a phone on mobile data is a different proposition from a 200 kB one, and the reader should get to make that call.

Read more

The post that stays

A blog's front page is the only page that's a statement. Everything else is chronology doing its job.

So pinned: true in a published post's header now holds a copy of it at the top of the first listing page. That's the whole feature, and the interesting part is everywhere it deliberately doesn't apply.

Only the front page

Type listings, tag listings, the RSS feed and the sitemap stay strictly chronological. A pin is a claim about what a visitor should read first, not a rewrite of when things were written. Someone subscribed to the feed gets posts in the order they happened, and a pinned post doesn't jump their queue every time it's toggled.

What happens as it ages

While the post is still recent enough to sit on page 1 by date, it appears exactly once — pinning it doesn't print it twice on the same screen. Once it has aged onto page 2, it appears in both places: at the top of page 1, and in its own chronological spot on page 2.

Read more

Some posts are conversations

Petr
I need a dialogue in a blog post.
./blog.sh
Write it in a chat fence. Name, colon, line.
Petr
That's it?
./blog.sh
A line without a colon continues the one above it.
Petr
And Markdown inside?
./blog.sh
Works. Bold, links, code — the usual.
Petr
Every other engine wants a plugin for this.
./blog.sh
Every other engine wants a plugin for tables, too.

The source of that exchange is exactly what you just read — speaker's name, a colon, the line:

Petr: I need a dialogue in a blog post.
./blog.sh: Write it in a chat fence. Name, colon, line.
Petr: That's it?

Wrap those lines in a code fence tagged chat and you're done.

You just read the feature. That's the whole documentation.

$ ./blog.sh add

From an empty terminal to a shareable draft, uncut.

One take, fifty-seven seconds, no commentary and no cuts. A phone with a folding keyboard, and ./blog.sh running over SSH on the screen behind it.

What you're watching:

Read more