Series./blog.sh v1.6

./blog.sh 1.6

./blog.sh 1.6 is out.

The build stopped rebuilding what nobody changed: a rebuild that changes nothing costs a seventh of what it did, an ordinary publish about two fifths. A post can be handed over as a file — by a script, a cron job, or the new page at /write/ on the blog itself, which sends from a phone over the SSH the server already has. Nothing new listens on the network.

Around those: a row of share controls under a post, an icon a tag can carry, a way out of the trash and the versions, and a photograph stored once instead of twice.

#blogsh #ruby #staticsite

The time between deciding and the site saying so

Publishing a post used to cost the size of the archive, not the size of the change. A post dated today alters a dozen files. The build rendered every page in the archive and read every one back off disk to find that out.

On the archive this engine was built around — 6,639 posts, on the server it lives on — that was 47 seconds for a rebuild that changed nothing, and 48 from pressing publish to the post being out. Fine for one post in the morning. Less fine for fixing a typo in ten of them.

What the build remembers now

1.6 keeps a record of what went into each page: the post, the template, the locale, the configuration, the engine itself. On the next build it renders only the pages whose record no longer matches, and the rest cost one stat each instead of a full render and a read-back.

The same archive, measured the same way: a rebuild that changes nothing takes a seventh of what it did. Publishing a post dated today takes about two fifths — 16 seconds of build and 2 of upload, where it was 48 — and the build says so itself: Pages already up to date, not rebuilt: 10,160. The ratio is what travels; the seconds are one machine's.

Read more

A post handed over as a file

Until 1.6 a post came from a person at a terminal. The wizard asked its questions, opened an editor, asked what to do at the end. Nothing else could write one: not a script, not a cron job, not a phone.

./blog.sh add post.md

does the same work with the markdown handed over instead of typed. No editor, no questions, no dialog at the end. A bare filename is looked for in incoming/, so the file can arrive by the same upload as the photographs, and it is deleted once the post is written.

Where the wizard would ask, this refuses

A person can be asked. A script cannot, so every question becomes a refusal that writes nothing: a photo that has not finished uploading, an empty body, a file that is not text, a second filename where one was expected. The alternative — guessing — was measured on the old code, where a byte-order mark in front of the header was taken for body text: three invisible bytes from a Windows editor and the post arrived with no title and no tags, named after the words "title:" and "tags:", exit 0. 1.6 reads the header through them.

--json: one object, nothing else

Read more

Written on a train

A reader who was thinking about moving to this engine wrote to say he would stay where he was. He manages his blog from a phone, on the road, and a blog that can only be worked from a terminal was no use to him however fast it built. He was right.

1.6 answers him with two things, and with one thing it refuses to do.

A page on the blog itself

Set write: true and the build publishes a small editor at /write/: a title, the text, tags, photographs and video, each picture with its description. It wears the blog it writes to — its name, its palette, its language — and it offers the blog's own tags as you type, the ones used in the last twelve months first, so a tag is tapped rather than spelt a second way. A preview shows the post in the blog's own stylesheets. A row of marks above the text wraps what is selected and comes off again on a second tap. Above the send button, a Draft | Publish switch.

Read more

Where a reader carries a post

A post gets read, and then it goes somewhere: into a toot, a message, an email to one person. Until now the reader did that by copying the address out of the bar. 1.6 puts a row of controls under the post for it — off unless share: names what you want, drawn in the order you name it.

share:
  - mastodon
  - bluesky
  - email
  - copy
  - system

What is prefilled, and what is not

The post's name and its address, where the target takes both. Facebook and LinkedIn take the address alone and read the name off the page. The reader writes the part that is theirs. bluesky, email, facebook, linkedin, threads and x are plain links, one address each, no script behind them.

Three that are not links

Read more

A badge that says what a post is about

Every post wears a date badge with an icon in it, and the icon says what kind of post it is: text, a picture, a video, a quote. On a blog where nearly everything is text, that icon is the same on nearly every card. Somebody running this engine put it plainly in issue #45: one sees the generic icon so often that it becomes invisible, or clutter.

1.6 lets a tag carry an icon instead.

tag_icons:
  - tag: "build"
    icon_svg: '<svg viewBox="0 0 24 24" …>…</svg>'
  - tag: "comments"
    icon: "chat"

It replaces rather than adds

On a post's date badge the tag's icon takes the place of the content-type icon; in the heading of /tag/<name>/ it takes the place of the generic tag icon. That was the requester's first point, and the one worth asking about rather than guessing: two icons side by side on a 60-pixel badge is not a badge, it is a puzzle. A tag with no entry changes nothing, so the generic icon is still there for everything you have not named.

Read more

Everything else in 1.6

Five posts have covered the things 1.6 is about. This one is the rest, in the order an operator meets them.

A way out of the trash and the versions

Both stores had a way back — restore, and the version picker in the post's properties — and no way out, so both grew for years and nothing said by how much. empty trash and empty versions each print what they are about to remove, in items and in megabytes, and require that count typed back before anything goes. empty versions keeps each post's newest version, because versions exist to answer "give me back what I just overwrote". doctor now notes a trash with posts in it, as a note rather than a fault: on the installation this engine was built around, the only way to see it was du on the server.

A photograph stored once

public.nosync/ used to hold a second copy of every picture. It holds a hardlink now, so the pictures take half of what they did — 1.8 GB on this project's installation. It saves nothing in a backup, and that was measured rather than assumed: 200 files under two names, 101 MB on disk, 201 MB in the backup. Nothing to do; the first build after the upgrade makes the links.

Read more