Yesterday I got the basic framework of my Gemini client up and going, and this evening I've been working on getting rendering of the gemtext in place. It's not the most tricky format to handle, so the parser and Textual widget to display the result have come together pretty quickly.
With this in place I've got the core of a working client now. Next up is going to be adding history with forward and backward navigation, then after that things like bookmarks and so on.
There's a lot to do yet, it'll be some time before this turns into a public project, but having it at least load up a document from a capsule means there's enough there for me to want to keep going and developing this.
Also, as I said yesterday, this is all home-grown and hand-crafted code. BlogMore has been tons of fun, but it's nice to kick off a significant bit of Python code where I'm writing it all myself. It's nice to be in that flow state once again.
I can't remember how and where I saw it, but just over a week ago I ran into Project Gemini. Somehow I've never read or seen anything about this before, which is pretty wild considering it's been on the go for around seven years now. As I read up on it I got more and more intrigued. I had the urge to do... something with it.
I think the thing I want to do, and I know I'm far from the first, is write a client for the terminal. I'm envisioning something very similar to Hike, but obviously only targeting the gemini protocol itself, and only handling and rendering gemtext.
I will, obviously, be looking to write this in Python, and of course, will be looking to use Textual, which means that it would be useful to have a Python client library for the protocol, and ideally a client library that is async. I did some searching, found client libraries, but none of them seemed to be async-first.
With this in mind, and to kick-start the project, last night I fired up Antigravity1 and got a library up and going. Wasat is the result. For the moment this should be considered alpha-status software (hence v0.0.1). I've done some very rudimentary testing and experimenting with it and, so far, so good. It's also proving to be a good tool with which to get to know the protocol. It also gives me another project to use to experiment with an agent (this being the first project I've started from scratch using Antigravity).
Over the next few days I'm going to toy with the library more, clean up the code, look for any issues, and then I'll start on the client application. That will be hand-built; no AI. I have some ideas of fun things I want to do, especially when it comes to handling gemtext.
It'll be nice to have a new pet project that's a hand-coded project. The first significant one since OldNews.
Getting back to Wasat itself: I believe it has everything necessary to allow for writing such a client (which, to be fair, isn't much -- that's kind of the point of the protocol). It also comes with a simple CLI built in, which can be run with python -m wasat (if it's just installed as a library) or with wasat (if installed globally, along with any command scripts). The command itself is just a simple download tool for a "page" in a "capsule". For example, I can grab the content of a test capsule I've created:
$ wasat gemini://davep.gemcities.com/
# Introduction
Hey! I'm Dave. Normally you'd find me at:
=> https://www.davep.org My web site
or
=> https://blog.davep.org My blog
amongst other places. But I discovered Gemini and I'm really curious about
the idea, so here I am giving GemCities a go to get to know things a little
better.
(with thanks to GemCities for providing a neat little service).
As for where this is all going: there's no direction, really. I've found a neat new thing that I didn't know about before, the idea sits well with me, and I want to explore it more. It also gives me an excuse to do a thing I really enjoy doing: writing terminal-based TUI applications for the sake of it, and especially writing one that works just how I want.
As normally happens, I then went on to tag the release on GitHub, followed by writing the blog post to announce the new version. While doing this, despite the fact that it wasn't necessary given the nature of the change, I decided to update BlogMore in my blog's repository. That's when things started to look odd.
I did the usual make
update but nothing new appeared. Now, it's not unheard of that I do this and no new version of BlogMore appears. Often I do it a couple more times and it's fine. So I kept trying every minute or two and still nothing. So I checked back on PyPI. Sure enough, a search showed that it had updated:
(The 16 minutes being about the time since I'd made the release), but when I clicked through it was showing the last version from a couple of days ago. Even when I looked at the release history it was saying the latest version was the previous version:
Odd.
At this point, depending on how I searched and where I went, I'd either see that my latest upload wasn't available, or I'd get a 500 error.
Leaving aside the whole issue of having an account on Twitter these days anyway, I felt it wasn't that useful to point people at a resource that seems to have never been updated, so I did raise an issue about that.
Digging around the status page at some point, despite the fact that the main display was green all the way, I did see a rise in "PyPI CDN Edge Errors". I'm not a web guy, I'm not an infrastructure guy, so I'm not really sure what this would mean, but it sounds like it's not a good thing.
Opening the graph to look longer term, it did seem today was a spike, with another spike quite some time ago.
At this point I left it a while, not announcing the new version of BlogMore. I came back some time later and, finally, I could see 2.43.0 was showing! Also, this seemed to coincide with the above graph calming down again.
Seeing this I went to upgrade BlogMore in my blog's repo/venv and this time it all worked.
Yay!
At that point I left it alone and went about my work day. However, I don't think whatever is going on is over. Despite the fact that it was showing BlogMore as being v2.43.0 earlier today, once things were settled, I just checked again as I started to write this and:
The search index on PyPI shows it as having been updated about 8 hours ago (as I write this), but the page itself shows that the latest version is from 2 days ago. At least installing it gives me 2.43.0:
After recently adding Mermaid and maths support to BlogMore, I got to thinking that it now has connections with a handful of third-party resources. While almost all of them are optional (only FontAwesome comes close to being a hard requirement), it does mean that there are resources the user doesn't directly supply or control, and for which the URLs are hard-coded in the source for BlogMore.
Of course, it's not an all-or-nothing setting; you can just set one of the values. So if you wanted to override the force_graph value it's enough to do:
Hopefully this adds some useful flexibility. As well as giving the option to use a different version of a resource, it also allows you to host your own copy and refer to that instead. Heck, it would even allow totally replacing a library with a different one that has the same API, should you ever find yourself in that situation.
When I first added it it simply dumped all of the data available about posts within a blog, meaning it was possible to do all sorts of things with the data outside of BlogMore itself. Having done this, and used the result to good effect (and also having seen at least one person also make use of it), I got to thinking that I should probably expose some other data that's going to be helpful.
Given that blogmore.el needs to dive into things like available categories and tags, and given that I'd also like to add support there for the recently-added series support, I realised that dumping those values as JSON would also be helpful.
So this release extends the dump command, adding a set of sub-commands:
posts: dumps the data about all posts in the blog (this is the default if you just run blogmore dump, so keeping the command backward-compatible).
categories: dumps all categories as slug/title pairs.
tags: dumps all tags as slug/title pairs.
series: dumps all series as slug/title pairs.
For example, getting all categories from this blog:
[["agentic-afterthoughts","Agentic Afterthoughts"],["buying-the-steam-controller","Buying the Steam Controller"],["edinburgh-fringe-2023","Edinburgh Fringe 2023"],["reading-2bit-files-for-fun","Reading 2bit files (for fun)"],["swift-til","Swift TIL"],["the-great-github-copilot-meltdown-of-2026","The Great GitHub Copilot Meltdown of 2026"],["the-virgin-east-coast-saga","The Virgin East Coast Saga"],["emacs-d","~/.emacs.d"]]
While this data could have been gathered from the content of the dump of posts, this approach makes it far easier to work with and more accessible; simply put, it means there's less work for the user to do if they just want all the tags (for example). With this, and with a little bit of caching in the code (because this does require parsing all the posts every time), I think it will make for a better approach to category, tag and (when I add it) series completion while writing a post.
It's probably bad news if I have a Saturday afternoon and evening spare, a fully-charged laptop and a comfy sofa. It seems that when that happens, something like BlogMore v2.41.0 happens. This is a release where I've added two features that could be generally useful, but which I'm unlikely to use in my own blogs.
The first, which to be fair is one I might use (I've used it in documentation plenty of times over the years), is optional Mermaid support. This is off by default, so has no overhead if you don't turn it on. It is turned on by setting with_mermaid to true. Even with this enabled, the Mermaid third-party scripts only get included on pages that include a Mermaid diagram, reducing the overhead.
```mermaidgraph TD A[Start] --> B[Process] B --> C{Decision} C -->|Yes| D[Success] C -->|No| E[Fail]```
If Mermaid is enabled the resulting page will show this:
There are, of course, all sorts of diagrams that can be used and I'm not going to go into them here, or in the BlogMore docs; Mermaid is well known enough and well-documented enough that anyone turning this on is likely to know what they're doing, or where to go to find out what to do.
The second new feature, which I am almost certainly never going to need to use on my blog, is LaTeX-style maths support. As with Mermaid, this is off by default and has no overhead if not used. Even when turned on with the with_maths setting, the external scripts will only be pulled into pages that include maths markup.
To use either, when turned on, you use the usual $ or $$ convention for LaTeX-maths-in-Markdown:
You can make some fun images using:
$$
z_{n+1} = z_n^2 + c
$$
We can say $z_{n+1} = z_n^2 + c$ inline too.
The result of the above will be something like:
Note that some care has been taken to ensure that ordinary use of a $, in currency values for example, is left unaffected. This can't be guaranteed in every possible case, so keep this in mind when turning on with_maths. From what I have read this is a common issue when using such markup.
Both these features were fun to add, with me planning out the implementation with Antigravity, and having a back and forth a couple of times to address issues and get it all working "just so". I'm especially pleased with the fact that it's done in a way where there is no overhead, even when either feature is enabled, if a page isn't showing a diagram or maths markup.
A quick update carrying on from this morning's release: as mentioned towards the end of the post, there was a small problem with the lint command where it would raise an error if a page or post linked to any page related to the new series feature.
v2.40.1 fixes this. The linter now knows about the series feature and the URLs it creates.
It's hardly a novel feature, plenty of blog engines support this sort of thing; I felt it was high time I added it to BlogMore too. The idea is simple: while categories provide a method of having "sub-blogs" within a blog (they also provide their own feeds), and while tags let you group together posts that touch on related subjects, a series will let you stitch together a collection of posts as directly relating to each other.
Adding a post to a series is as simple as adding a series to the frontmatter of a post.
title:Five days with Copilotcategory:AItags:Python, AI, LLM, Copilot, GitHub, BlogMore, codingdate:2026-02-20 15:46:00 +0000series:Agentic Afterthoughts
With this done, when a reader is viewing an individual post that is within a series, some new navigation appears at the top and bottom of the post that lets them know about the series, and also offers them easy navigation to the previous and next posts.
Also, if the reader were to click on the series title, it will take them to an archive page for that series, where the posts are presented in the usual pagination format, but where they're also presented in series order. To be clear: series order is always chronological order.
To help encourage further post discovery, if one or more series are available, a new Series link appears at the top of the blog. This links to an index of every series available.
Sorted in alphabetical order, the list also shows how many posts are in each series and, if reading time is turned on, the total reading time will be shown.
Worth noting is the fact that a post doesn't have to be restricted to just one series. If you need a post to be in two or more series, you can provide the series as a list:
title:My post that crosses the streamscategory:Testingtags:BlogMore, Series, Testdate:2022-02-22 22:22:22 +2200series:-Experiments with blogging-Eating my own dog food-Testing thing
The post will then show navigation for every one of those series, and the post will be in each series' archive page.
Having added this, I've also updated the output of the dump command so that series (the series list as provided in the frontmatter), safe_series (the series list in their URL-friendly slug forms) and series_pair (a list of series title and slug pairs) properties are available.
As of the time of writing this, I have noticed one issue. The linter isn't currently aware of the URLs that are created as part of the series facility. So if you have a page or post that links to the index, or links to a particular series, there will be errors:
blogmore lint
Linting site in content...
ERROR: posts/2026/06/2026-06-06-blogmore-v2-40-0.md: Link points to non-existent internal path: /series/ (resolved to /series/)
ERROR: posts/2026/06/2026-06-06-blogmore-v2-40-0.md: Link points to non-existent internal path: /series/ (resolved to /series/)
ERROR: posts/2026/06/2026-06-06-blogmore-v2-40-0.md: Link points to non-existent internal path: /series/the-virgin-east-coast-saga/ (resolved to /series/the-virgin-east-coast-saga/)
Linting complete: 377 post(s), 4 page(s), 3 error(s), 0 warning(s) [0.45s].
This should be a straightforward fix which I'll make in a follow-up release.
BlogMorev2.39.0 has a couple of changes, one more or less cosmetic, the other a new feature that I probably should have added right near the start of the project.
First, the cosmetic: in pages and posts, when you use headings (#, ##, etc.), a ¶ is added to the end of each heading, along with some show-on-mouse-hover styling, to make it easy to grab the anchor of a particular section of a page1. A problem I'd noticed with this recently is that the ¶ shows up in the search text, and also in the content of all the feeds. While not a problem as such, it felt untidy.
So, with this release that particular ¶ character is stripped from the search index and from the feeds.
The second change is a facility that should be useful if you have legacy URLs that you really must have land on an actual page or post: redirection support.
This facility is provided by a new redirect_from frontmatter property. From now on, a page or a post can declare which URLs should be redirected to it. So, if you have a post that, given your current post location setup, would land at /2006/06/06/hello-world/, but in some other incarnation of your blog lived at /posts/2006-06-06-hello-world.html, you can now set this in the frontmatter:
redirect_from:/posts/2006-06-06-hello-world.html
and the old URL will be redirected to the new one. This is achieved by simply creating a very minimal HTML file at the old location, which contains a redirection. The content of the file will end up looking something like this:
<!DOCTYPE html><html><head><metacharset="utf-8"><title>Redirecting...</title><linkrel="canonical"href="https://example.com/2006/06/06/hello-world/"><metahttp-equiv="refresh"content="0; url=/2006/06/06/hello-world/"></head><body><p>Redirecting to <ahref="/2006/06/06/hello-world/">/2006/06/06/hello-world/</a>...</p></body></html>
If a blog has migrated between locations and/or generation tools over the years, and there are a number of different legacy URLs out there still pointing to a post you really want to keep readable to all, you can also redirect from multiple URLs:
While all of this would have been possible anyway, by creating the appropriate file, with the appropriate content, in the extras directory, this approach helps automate the process and also means that the redirection information lives with the page or post being redirected to.