Finally putting some money into a decent chair for my desk at home. The one I have is showing its age, not to mention loosing its comfort. Who would’ve though a sub $100 desk chair from Officeworks would be lacking in quality? 😛
Interesting odometer reading this evening.

Gallery of Fake Logo For Test Organisations
In lieu of anything else, I thought I’d put together a gallery of the logos I use for test organisations. I occasionally need to create fake organisations for testing at work, and to add a bit of amusement to the mix, I sometimes make fake logo for them. These logos are typically made using ChatGPT, although if I’m particularly bored, I sometimes touched them up by hand (nothing too drastic, usually things like cropping or adding titles). Most of the fake organisation are film and media production studios, as that’s typically the client base of our product.
I do this mainly for fun, but there is some utility to it. A user can be a member of zero or more organisations, and can change the one they’re acting in at any time. Having a unique avatar for each one helps in distinguishing which one I have active. I do get cute with the names, and for that, I make no apologies. 🙂
Had another look at how I could export an Obsidian kanban board as an actual kanban board today. I thought I’d through ChatGTP at the problem, and see whether it could produce a Go program that took the markdown representation of a kanban board and reproduce it as a HTML file.
Here was the prompt I used:
Please write a Go program which will take a Markdown version of an Obsidian kanban note, parse it, and convert it into a HTML page.The markdown representation of a kanban note is as follows:
- A H2 header represents a column, with the H2 text value representing the column name.
- Between each successive H2 header are the cards for that column. Each one begins as a TODO item. The card contents is indented and can be any arbitrary Markdown.
The generated HTML must have the following traits:
- Each column must be wrapped within a div.
- Each card within that column must be wrapped with a separate div.
- The body of each card must be rendered from Markdown as HTML.
The tool must take the Markdown as standard in, and write the HTML to standard out.
ChatGPT came up with something, and after checking it for obvious errors, I prepared a test board and gave it a run. Here’s the test board I used:

Here’s how it was rendered in HTML:

A good first attempt, and I was impressed at how it actually styled the page to actually display the columns as columns1. I was not expecting that. There were a few things that were missing, such as rendering card bodies. In fact, a keen observer may note that some of the TODOs that appear in the In Progress column actually come from the card body itself.
I requested these changes from ChatGPT and it had another go. And this is where it ran into trouble:

One issue was that the generated code decided to scan the input line-by-line, and maintain state as to whether it’s parsing a column heading, card title, or a card body. But it was not properly resetting this state when encountering the next column header (note how “#blocked Will not do” appears below the In Progress heading when it should be in the card above it). This also explains why the columns no longer showed up — there were divs that weren’t being closed.
So it was time for me to take over. I fixed a few bugs and added handling of the card body indentation. The end results looks like this:

Good enough for what I need.
All in all, it was an interesting experiment getting ChatGPT to start this off, and I’m impressed on how much it managed to do from the jump. But I’d be hesitant to say that what it produced is high quality, maintainable code. It was a little difficult to change, and it’s certainly wouldn’t be the way I’d choose to do it. If this was anything more than something for my own use, I probably would’ve junked it and started from scratch2. But hey, it works. And for what I need, it’s better than not existing at all.
If anyone’s interested in seeing the code, you can check it out here.
Saw hot air balloons out flying this morning. Usually I see them in autumn, yet we’re not even half way through summer. Probably the earliest in the calendar year I’ve seem them operate. That said, it’s pretty good conditions for them this morning, if not a little warm.
I started using the Obsidian Kanban plugin to organise work for my team. Working quite well for my needs. Only problem is, I can’t export it as a Kanban board to an interested team member. It seems like only exporting the raw Markdown is supported. I need to have a think about what I can do here.
Keyboard Maestro is coming into its own as a way for scheduling recurring tasks. I’ve just set one up for a daily report I need to run. It’s little more than a shell script so I probably could’ve used crontab to do it, but is so much easier configuring and testing it in Keyboard Maestro.

Spotlight has stopped indexing my apps again. Not sure what broke between now and the last time I tried to fix it, but I think it’s time to move to something else. So I’m giving Raycast a try. And yeah, so far so good. At least it lets me launch apps.
Ugh, you know you’re getting old when you start seeing remakes of things you were too old for the first time around. 😏
On the train home, and all the phone screens from the seated passengers act like tiny mirrors, reflecting the sun to where I stand. Have to face backwards to keep from getting blinded. Even doing that doesn’t completely stop the flashes of light. 🙈
🧑💻 New post on TIL Computer: A Way To Resolve Redelivered Messages Stuck In A NATS Jetstream
For anyone else interested in the trackside signage of Victorian railways. What got me looking was learning about coast boards. Seems to be instructions to the driver on what to set the train’s power output.
Solved Mini 287 on lex.games in 2 minutes, 29 seconds. No reveals.
⬜⬜⬜⬜⬛
⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜
⬛⬜⬜⬜⬛
Manuel Moreale wrote about Spotify:
[T]hey keep adding all these bizarre new things that I’m always left wondering if I’m a very odd user and other people’s use of Spotify is so much different than mine. Like who watches video podcasts on Spotify? Why is a music app getting into videos?
I’ve not used any feature built by Spotify that didn’t directly relate to playing music as audio, nor do I know anyone that does. They’re more likely to get in my way, making them less than useless to me. And yes, this includes podcasts1.
Who knows, maybe 2025 is the year I jump off the Spotify ship.
I’ve said this before, but now that I’ve got the means of (legally) acquiring DRM-free music from mainstream artists, I may aim to do likewise.
-
I consider myself awesome for introducing everyone I know to Pocket Casts and the open podcast ecosystem. 😛 ↩︎
I recently got a new phone, a Pixel 9 Pro, which meant I needed to bring Alto Player up to date. I probably could’ve gotten away using the version I was using on my Pixel 6. But I didn’t have a binary build, and I needed to upgrade Gradle anyway, so I decided to spend a bit of time bringing it up to date to API version 35, the version used in Android 15.0. Fortunately it was only a few hours in total, and once I got it running in the simulator, I side-loaded it onto my phone and started using it.
It worked, but there were some significant UI issues. The title-bar bled into the status bar, the album image in the Now Playing widget was cropped by the curved corners of the phone, and the media notification didn’t display playback controls.

I set about fixing these issues today, starting with the title-bar and Now Playing widget. These was an issue with the views not respecting the window insets, and after a quick Google search, I found this article showing how one could resolve this by adding a ViewCompat.setOnApplyWindowInsetsListener and reacting to it by adjusting the margins of the view.
val topLevelLayout = findViewById(R.id.top_level_layout) as CoordinatorLayout
ViewCompat.setOnApplyWindowInsetsListener(topLevelLayout) { v, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = insets.left
bottomMargin = insets.bottom
rightMargin = insets.right
// applying a top margin here would work, but will not have the toolbar
// background colour "bleed" into the status bar, which is what I want.
}
val t = v.findViewById<Toolbar>(R.id.toolbar)
t.updateLayoutParams<ViewGroup.MarginLayoutParams> {
// make the toolbar a little "narrower" than full height
topMargin = insets.top * 3 / 4
}
WindowInsetsCompat.CONSUMED
}
It took a few attempts, but I managed to get this working. Just using the top inset for the toolbar margin made it a little larger than I liked, so I adjusted the height to be 75% of the inset. This means the toolbar will actually encroach into the area reserved for cut-outs like the front-facing camera. This is arguably not something a “real” Android apps should do, but this is just for me and my phone so it’s fine.
I went through a few iterations of the album artwork cutoff on the bottom right corner trying to find something I liked. I tried bringing in the horizontal margins a little, but I didn’t like the alignment of the album art in the player, particularly compared to the covers that appear in the track list screen. One thing I didn’t try was raising the bottom margin so that it would fit “above” the curve. But those corners curve in quite a bit, and doing this would sacrifice a lot of vertical space. So I settled on hiding the album art altogether. It’s a bit of a shame to loose it, but at least it looks neater now.
The next thing I looked at was fixing the playback controls in the media notification. After some investigating, I found that this was because I was not setting the available actions in the PlaybackStateCompat builder. This, if I understand correctly, is used to communicate to various systems the current state of the playing media: what the track name is, whether it’s playing, whether one can skip forward or back. I have my own types for tracking this information — which is probably not correct but I wasn’t completely sure as to what I was doing at the time with Android’s media stack1 — and when I needed to convert this to a type understood by the framework, I made instances of this builder without setting the available actions. Earlier versions of Android seemed not to care, and the controls always appeared on the notification. But I guess they changed that.

One other thing I needed to do was to explicitly ask the user permission to show a notification before I could publish one. This is also relatively new: my experience with Android goes back to the early days where these permissions were disclosed up front when the app was installed. But I can completely understand why they changed it, as it was easy to simply tap through those screens with reading them. I am wondering whether media playback notifications are in some way exempt from these permission checks, as I was actually getting one to appear before I made this changes. But I figured it was probably worth doing anyway, so I added this permission request on first launch. Arguably I should be asking for this permission when playback starts, but again, this is just for me.
One final thing I needed to address were long album titles. The text view displaying the the album title had a width that autosized to the title itself, and the start and end constraints were set such that it appears centred in the view. This worked for “normal” length titles but when the length became excessive, the text view would fill the entire width of the screen and the title will appear left justified.

The fix for this was to set the text width to be calculated by the start and end constraints (setting layout_width
to 0dp
), bringing in the margins a little, and making the label text centre justified. I did this already for the track title, so it was easy to do this here too. Not sure why I didn’t do it earlier, or why I haven’t done it for the artist’s name yet.

This was all rushed, and I’ll admit I wasn’t 100% sure what I was doing. I was going down the route of trial-and-error to get this working, mixed in with web searches and a trip to ChatGPT. And yeah, the methods I used won’t make this a portable Android app that would work on every phone out there. But I’m reasonably happy with how it turned out.
-
This is still true to this day. ↩︎
📺 Wallace & Gromit: Vengeance Most Fowl (2024)

Seems like Substack is not giving up on their Twitter clone. I only just discovered that if you tap on a post’s author, it goes to their Notes page. Not sure what I was expecting (maybe an About page, but would that make sense given their target market?) but I wasn’t expecting this.
Thanks for my new found fondness of buying mainstream music instead of streaming it, I needed a way to get these albums into Alto Catalogue. There exists a feature for fetching and importing tracks from a Zip referenced by a URL. This works great for albums bought in Bandcamp, but less so for any tracks I may have on my local machine.

I’ve managed to get Alto Catalogue building again after updating Webpack and a few NPM packages, so in theory, I could add an Upload Zip file action. But there’s more to this than simply accepting and unpacking a Zip file. I have to read the metadata, maybe even preview the tracks that will be imported, just in case I’m importing something I rather not (I did see this once, where zipping a bunch of tracks in the Finder introduced duplicates). This already exists for Zip files that are downloadable online.
I had a though about what my options are, until I remembered that I had a Gokapi instance running in Pikapods. So I tried using that to temporarily host the Zip file with a publicly available URL that could be read by Alto Catalouge.
The only problem is my internet upload speed is sooooo sloooooow. The Gokapi instance is hosted in Europe, and I suspect the instance itself is a little underpowered. So uploading 100 MB Zip files would take a fair bit of time: maybe 15-30 minutes. When I tried doing this via the web frontend, the connection timed out.
Fortunately, Gokapi has an API and one of the methods allows you to upload a file in “chunks,” which Gokapi will assemble back into the original file. Even better is that this chunking can be uploaded in parallel.
So I built a CLI tool which made of this chunking API to upload the Zip files. Once the upload is complete, the tool will display the hot-link URL, which I can copy-and-paste into Alto Catalogue.
The whole process isn’t fast (again, slow upload speeds). But it works, and I can use this tool to queue a bunch of uploads and let it do its thing while I’m doing something else. I really like tools that do this, where you’re not forced to babysitting them through the process.
There are a few limitations with it. It doesn’t allow for an awful lot of customisations on the lifecycle of the uploaded file. And the tool stalled out once when my computer went to sleep, and I had to start the upload from scratch. I could probably add something to track the chunks that were successful, allowing one to continue a stalled upload. If this happens frequently, I may look more into adding this.
But even so, this could be a useful addition to my use of Gokapi for transferring temporary files. If you think this might be useful to you, you can find the tool here.
Bit surprised to see this appear in my washing machine after washing some new clothes I bought this week.


I’ve started buying music via Qobuz, which offers DRM-free MP3 and FLACS of mainstream albums. So far it’s been really good, although I wish they offered a way to download an album as a Zip file, rather than require you to do so track by track (they have a download manager but, come on: it’s 2025).