All Posts

Page 1 of 23

Match API routes with URLPattern in a Workers function

I was wiring a handful of API routes on a Cloudflare Worker and didn’t want a framework for it. I’d usually reach for a regex, or split the pathname and switch on segments. I didn’t know there was a web-standard way to do this, but there is! URLPattern matches parts of a URL with a syntax that looks a lot...

Mount named Docker volumes into Backrest as regular filesystems

I’ve been overhauling (see: adding) backups for all the stuff I run in Docker at home. I used Backrest, which uses restic, and it’s been a pleasant experience. The trick I found was to mount named Docker volumes in the docker-compose so they can be backed up as regular filesystems, rather than just wholesale backing up Docker volumes without naming...

Bind Chrome Tab Search to Command-P with macOS App Shortcuts

I wanted ⌘P to open Chrome’s Tab Search to leverage my muscle memory from VS Code. Chrome doesn’t let you rebind its built-in shortcuts — ⌘P is wired to Print, Tab Search is wired to ⌘⇧A, and neither shows up in Chrome’s shortcut settings (chrome://settings/shortcuts). On macOS you can get there with App Shortcuts. You’re not changing Chrome’s internal bindings,...

Give coding agents a tmux session with your real shell environment

Note: Using tmux and other more-or-less built for agent multiplexers is a slightly different approach to the one I describe here - I’m talking about using tmux from an agent instead of the agent running shell commands directly. Just saying that upfront to avoid confusion! Coding agents spawn a fresh shell by default. That shell is usually set up slightly...

Building resilient personal apps

I’ve written before about passion projects and how taking care of them feels more like tending a garden than running a product. Mowing the lawn and weeding, with the occasional prune. That metaphor still feels right to me, but I’ve started thinking more concretely about what happens when a project reaches the point where I actually use it most days,...

How I publish Standard.site records from a Rails Bluesky delivery channel

Aotearoa, Again is a Rails app that publishes a daily colourised photo edition and fans it out to email, Instagram, Facebook, YouTube Shorts, and now Bluesky. I wanted those Bluesky posts to pick up the enhanced link cards Bluesky renders for Standard.site documents, instead of a plain Open Graph preview. Standard.site is a set of AT Protocol lexicons for publications...

Re-enable SVG after Rails blocks untrusted libvips loaders

I patched Virtualtrails for GHSA-xr9x-r78c-5hrm (CVE-2026-66066), the Active Storage arbitrary file read via libvips. The fix has Active Storage call Vips.block_untrusted(true) at boot, which turns off libvips loaders marked “unfuzzed” (unsafe for untrusted content). That includes SVG. On Virtualtrails I render share cards as SVG and convert them to PNG with libvips. After the upgrade, that conversion failed with a...

When VPN DNS works in dig but not in curl on macOS

I occasionally use openfortivpn on macOS. The VPN comes up fine, but internal hostnames still fail in apps. curl can’t resolve them. Browsers can’t either. Meanwhile dig internal.example.com returns the right answer. openfortivpn has added VPN nameservers to /etc/resolv.conf, but on macOS that file isn’t what curl, browsers, or ping use. dig still reads it, which is why lookups can...

Bind XPath variables in Nokogiri instead of string interpolation

When you look up XML nodes by attribute value in Nokogiri, it’s tempting to interpolate the value straight into the XPath: doc.at_xpath("//item[@id='#{id}']") That works when id is predictable. It’s shakier when the value comes from a user upload, an API payload, or anything else you don’t control. A value like foo" or @id="bar can break out of the string literal...

A generic Stimulus controller for toggling fieldsets

I was building an achievement editor in Virtualtrails where route creators can place a milestone either as a distance along the route, or as a latitude/longitude pair that gets converted server-side. The form needs to offer both options, but only one should be active at a time — and only the active panel’s fields should be submitted. My first iteration...

← Previous Page: 1 of 23