Very niche software, part 1
Before the advent of LLMs, my hobby was failing to finish software projects that I hoped might be generally useful to a wider audience. But now that the cost of writing software is effectively zero, it feels like there isn’t much point making it for other people. So my new hobby is applying absurd levels of polish to incredibly niche software projects with a target audience of one. It turns out this is a lot of fun! One of these niche projects that I worked on recently is Blue Friday.
Blue Friday’s niche is the intersection of a few specific sets: people who watch repeats of old Top of the Pops episodes on BBC4 and who like to lurk on Bluesky’s #totp hashtag while doing so. Every Friday night, if it’s not cancelled because of snooker or golf, BBC4 in the UK repeats old episodes of Top of the Pops and an enthusiastic community of fans congregate on Bluesky to reminisce/laugh/shitpost/memepost about ye olde times. In my household it’s tradition to watch with a glass of gewürztraminer and try to guess which awful song will be the one that makes the dogs leave the room.
The problem is that Bluesky’s web UI is really bad
for consuming the flood of #totp content.
Posts scroll by too fast
and there’s no categorisation or separation,
so it’s pot luck which ones you see and which you miss.
Gradually it dawned on me that I need a bespoke dashboard,
specially crafted to tesselate with the shape and character of the #totp feed.
Blue Friday is my answer to that.
With all side-projects, part of the challenge is hosting it as cheaply as possible. My go-to for that is often Cloudflare Pages and/or Cloudflare Workers, so my instincts here were no different. Blue Friday has an interesting kink in that the backend is stateful, but it does not need multi-tenancy. It has the broadcast window and a stream of posts, but no modelling of users on the backend. That’s a great fit for an SQLite-backed Durable Object, because with one broadcast window a week I can easily stay within the limits of the free usage tier. The architecture, if you can even call it that, is quite simple:

Requests to app.bluefriday.uk
return a single-page app
built with Vite and Lit.
Backend requests are then directed to a separate domain,
backend.bluefriday.uk,
which hosts the worker.
The main endpoint in the worker is /events,
which opens a server-sent events (SSE) connection
between the durable object and the client.
Because it’s a cross-origin request,
we include a permissive Access-Control-Allow-Origin header with the response.
The worker also has a /wake endpoint,
hit from cron every 5 minutes during a broadcast window
(conservatively set between 5pm UTC on Friday and 3am UTC on Saturday),
and a /status endpoint which is just there as a debugging hook.
The durable object is evicted when idle,
so we use an alarm
to keep it alive during the broadcast window.
The /wake endpoint in the worker
calls a wake method on the durable object,
which upserts a broadcast row in SQLite
then sets the alarm to fire in 30 seconds.
When the alarm fires,
it checks the elapsed time since the last call to wake.
If this interval is less than an hour,
it sets another 30-second alarm
and the cycle continues.
But when an hour without wake calls is detected,
the durable object is forced back to the dormant state
and all activity ceases.
Being dormant for 6-and-a-half days of the week
is what keeps the durable object within the limits for free usage.
When it’s awake,
the durable object reads posts from Jetstream,
the Bluesky firehose.
It filters incoming posts for the #totp hashtag
or for replies to posts that were previously tracked.
Each post is then hydrated with profile information of the author,
firstly from cache
or falling back to Bluesky’s AppView
in the case of a cache miss.
The resulting filtered, hydrated posts
become the live feed for Blue Friday.
In addition to the live feed,
the durable object also publishes
a popular feed of the most engaged posts
and a number of meme feeds which are aggregated
by matching posts against patterns defined in config.
All of this gets pushed back to clients
through the open SSE connections.
Clients that show up outside of a broadcast window
receive the old persisted state from the last broadcast.
On the frontend I tried to keep it simple too:

There are basically 2 feed columns, one for everyone’s posts and another for your own. Within each column, filters can be applied to help zero in on specific subsets or memes. For anything that’s not available in Blue Friday’s UI, there’s a link out to it on Bluesky instead. Actions such as posting and liking go directly to Bluesky too, not the Blue Friday backend.
And that’s all there is.
No users table, no analytics, no KPIs, no roadmap.
It’s an entirely frivolous project
and I’m the only person who will ever use it,
but perhaps that’s why I actually managed to ship something this time.
There’s no imaginary audience to endlessly polish for;
just me, Milo, Ralph and a glass of gewürztraminer.