Dressing a site differently no longer means editing the engine — and the archive no longer has to be taken on faith. Around the four appearance settings this release started with grew the tools that read the whole archive and answer for it, a media contract that survives re-imports, an undo for every edit, and comment moderation that works from the Mastodon client you already have open.
Dress the site from the wizard. Your own stylesheet, your own menu, the sidebar switch and a lead image above the title are settings now — site.extra_css, nav:, layout.sidebar, layout.hero — and ./style.sh can write every one of them. A skin finally survives git pull. Every default is the layout the engine already had.
./blog.sh check reads the archive and says what is broken in it. Missing media, images the build would drop caption and all, internal links nothing answers at, orphaned media directories, files a post no longer names, two series names an edit apart, one old address claimed by two posts. It only ever reports, exits non-zero on errors alone, and its summary totals the archive, not the screen. --online asks the web about outbound links too.
./blog.sh export walks the whole archive back out — a tree of markdown files in Jekyll's layout, media included, redirects in the shape jekyll-redirect-from reads. What markdown cannot say travels as HTML carrying its own definition, which the importer reads back — so export plus import moves an installation, field by field.
A media file's identity is its address. Every entry records where its file came from, so a re-import fetches only what the archive does not hold — a second run makes no requests. An import only ever adds: nothing on disk is ever replaced, a returning picture takes back the name it had here, and a download the archive's copy outranks is counted in the summary — the one signal the source has drifted.
Every edit is undoable. The previous text is kept before every overwrite — ten versions per post, travelling with it to the trash and back — and [v] in props walks them as a list, preview under the cursor, one keypress to restore.
Only the comments you star, if you want it that way. comments.approval: fav publishes a reply once you favourite it, from whatever client you use; your own replies need no star. A moderated page stops making third-party requests entirely. And a reply's pictures appear with it now — thumbnails under the words, on the moderated list and the live thread alike.
Posts learned new shapes. page: true gives a post a permanent address at the root; series: groups posts with part-to-part navigation and a listing from the second part on; unlisted: true keeps a published post reachable but out of every listing, feed and index. Every post page carries reading time and, from four headings up, a table of contents. And a dead end has a page of its own now — menu, search field, signpost.
./blog.sh stats counts the archive from the posts on disk: years, types, words with both mean and median, tags, media, platforms. No build, no network, --json for the raw numbers.
Also: the publishing queue carries a post in one move and the schedule prompt says how to reach today's slot; a feed per tag the menu names; fediverse:creator on shared links; a maintained robots.txt list for AI crawlers; Tab completes paths in the import wizard; the interactive screens repaint in place instead of scrolling; and the example config explains how to read itself.
Upgrading — nothing to migrate. The one visual change that arrives unasked: the menu bar follows you down the page, and the menu that used to repeat under the content went with it. The next deploy is a full one. Two things scripts and tree re-imports should know: a wrapped ./blog.sh rebuild now exits with the lock's own code when it steps aside, and a markdown-tree re-import no longer invents visible captions from alt texts.
Full notes in CHANGELOG.md.
MIT licensed. Documentation · blogsh.app