Blog

Thoughts, ramblings and ideas, plus my worklog. Mostly related to web development

What should actually go in your CLAUDE.md?

The guidance contradicts itself and almost none of it is dated, so here's a map of who says what, what the research supports, and how AGENTS.md fits in.

article
astro-d1-search: site search for Astro in one integration

I extracted the search implementation I built for this site into an npm package, so you can add full-text search to your own Astro site

article
A native iOS app for Otter

Otter now has a proper native iOS app, written in SwiftUI. No web views anywhere: browsing bookmarks and saving new ones both go through Otter’s REST API directly, with the first page of bookmarks cached to disk so the list renders instantly at launch and refreshes in the background. The difference in performance over loading the web app is enormous.

Sign-in is OAuth 2.1 with PKCE, using RFC 7591 dynamic client registration — you type your instance address and that’s it, no client ID to configure. Tokens live in a keychain group shared with the extensions.

Saving works from wherever you happen to be: the system share sheet, the + button, an otter://save?url=… deep link, or a “Save Bookmark” Shortcuts action. The Xcode project also ships a Safari web extension for saving the current page, on both iOS and macOS.

worklog
Building site search with Astro and Cloudflare D1

Replacing Algolia on this very website with a self-owned, full-text search API built on Cloudflare D1 and SQLite FTS5, searchable from the site itself and from Raycast

article
text-wrap, overflow-wrap and word-break: which one do I actually want?

A practical guide to CSS's confusingly-similar text wrapping and breaking properties, and when to reach for each one

article
Lab experiments + ZUI playground

A batch of new experiments on lab.zander.wtf: a keyboard-only navigation demo, a clock quiz for practising telling the time (with relative-time questions), and a procedural page — an endless, self-generating page that builds itself as you scroll.

The procedural idea also landed in the ZUI docs as a procedural playground: an infinite page generated entirely from ZUI components. Alongside that, ZUI grew a VS Code extension with version-accurate completions, a Raycast extension for reference lookups, and a redesigned docs homepage.

worklog
Letterboxd watchlist for Radarr

Added a Radarr-compatible Letterboxd watchlist endpoint to my-api-tools, my growing collection of personal APIs running on a Cloudflare Worker. It scrapes my Letterboxd watchlist and serves it in a format Radarr understands as an import list — add a film on Letterboxd, and it shows up ready to grab.

worklog
The Otter ecosystem

One bookmarking app, many faces — a tour of the web app, browser extensions, iOS share extension, Raycast extension, terminal UI, and desktop experiment that make up Otter.

article
The ZUI ecosystem

The tooling that has grown up around my CSS-first UI library — framework components, a VS Code extension, a Raycast extension, and a publishable Astro docs theme.

article
Zero: my project factory

How a single GitHub Actions workflow takes me from an idea on my phone to a scaffolded, deployed-ready project with a database, secrets, and Claude already building it.

article
zed-astro-starter

Put together zed-astro-starter, an opinionated Astro starter ready to deploy to Cloudflare Workers. It pairs Astro 7 with ZUI for styling, Vite+ as a unified toolchain for formatting, linting, type checking and testing, and GitHub Actions for deploys plus a supply-chain scan on every branch. Clone it, pnpm install, and you’re building.

worklog
The modern CSS hiding inside ZUI

A tour of the newer CSS features powering my UI library, from automatic contrast text and a derived colour system to variants without bloat and popovers with no JavaScript.

article
My zero-to-one stack

The tech stack I use to ship new side projects quickly, why I moved away from Supabase, and how a starter template changed how I build.

article
Launched fonts.zander.wtf

Launched fonts.zander.wtf, a curated showcase of free, high-quality typefaces. Every font is one I’d actually use — no endless scrolling through Google Fonts trying to separate the gems from the filler. Each typeface gets its own detail page with live previews, weight and OpenType feature toggles, and install options via Fontsource or download.

It also ships a font-picker agent skill, so AI coding agents can pick fonts and pairings from the catalogue when scaffolding a new site — there’s a /skill/ page explaining how to install it.

worklog
React Status Bar v1

Released react-status-bar v1.0.0 — a lightweight, portal-based status bar system for React. Any component can push UI into a shared bar with <StatusBar>, and a <StatusBarViewport> aggregates the entries and renders them, optionally portaled anywhere in the DOM.

It’s built on an external store + useSyncExternalStore, so a status change re-renders only the viewport reading that scope — never the producer tree. Entries sort by priority, sides can be pinned (align="start" / align="end"), and overflow="clip" hides the least-important entries when they don’t fit, VS Code-style. SSR-safe and screen-reader friendly too.

There’s a demo site if you want to poke at it.

worklog
Spotify Favourites

Built spotify-fave-artists to answer a question Spotify gets wrong: who are my actual favourite artists and albums? Put one song on repeat for a week and Spotify will happily call that artist your favourite. This disagrees.

Instead of counting plays, it ranks by album engagement — how many distinct tracks of each album you’ve worked through, breadth first and depth as the tie-breaker. One track played 400 times counts for nothing; steadily making your way through whole albums counts for everything. It runs locally from a Spotify data export, works fully offline, and can enrich with the Spotify Web API for true album coverage plus cover art.

There’s a live preview of my own results.

worklog
Add a projects page

Added a projects section to the site — side-projects, packages and other things I’ve made or am making. It pulls everything together in one place, from older work like Kickoff and Design System Utils through to the recent run of tools like Otter, ZUI and xtractr. I also surfaced a few of them on the homepage while I was at it.

worklog
Using hunk as a diff viewer in lazygit

A short guide to getting hunk rendering inside lazygit's diff panel on macOS

article
agent-skills

Set up agent-skills to manage the various AI agent skills I use, shared across Claude Code, Amp and Codex.

My own skills live in the repo as the source of truth. Third-party skills aren’t vendored — they’re pinned in a skills-lock.json by source repo and re-fetched with the skills CLI, like a package lockfile. An install.sh restores everything and symlinks each skill into the right place for every agent, so a new machine is one clone and one command away from my full setup.

worklog
Launched Reps

Built Reps — a daily JavaScript puzzle for your coding muscles. Think Wordle, but you write a small JavaScript function and it runs against three tests right in your browser.

There are 40 puzzles covering algorithms and data transformations, with difficulty labels, an animated test runner and stat tracking. It uses the Monaco editor, runs your code in a Web Worker with timeout protection (so an infinite loop won’t take the tab down with it), keeps progress in localStorage, and has Wordle-style spoiler-free sharing. Built with Vite and ZUI, deployed to Cloudflare Workers.

worklog
Launched lab.zander.wtf

Spun up lab.zander.wtf, a home for experiments, little tools and demos. Built with Astro and styled with ZUI, each item gets its own page with a bespoke design.

To kick it off I rescued a bunch of old experiments from my ancient lab.martineau.tv site and migrated them across — CSS-only icons, pure-CSS media player controls, a cloze test generator and more, some dating back to 2009.

The new headline addition is the Data Transformation Dojo: an interactive React trainer for reshaping data in JavaScript, with 20 real-world map/filter/reduce challenges, hints and a live test runner.

worklog
Otter web v1.1.0

Otter shipped otter-web-v1.1.0. The headline addition is shareable tag and collection views, so you can hand someone a link to a curated set of bookmarks. It also fixes stale feed data and a broken trash button.

As an example, here’s my app:mac tag view in action.

worklog
zed-stack-starter

Put together zed-stack-starter, a modern full-stack React starter so I can stop reassembling the same pieces every time I start something new. It’s React 19 with TanStack Router, a Hono API, better-auth, Drizzle ORM on Postgres, and ZUI for the UI — all deployed to Cloudflare Workers.

worklog
Otter web v1.0.0

Otter reached its otter-web-v1.0.0 milestone. I migrated from Supabase to a better-auth / Drizzle ORM combo instead. The db is hosted with Neon which gives me a lot more flexibility to work on a greater number of side-projects.

The headline features are AI-generated summaries for articles and a companion Raycast extension for saving and searching bookmarks without leaving the keyboard.

A quick follow-up landed straight after with v1.0.1 (endpoint and AI model improvements).

worklog
UK Primary School Maths

Made uk-primary-school-maths, a little learning site to explore key maths concepts year by year. It explains the ideas behind each topic and lets you practise with checkable activities. A nice change of pace from the usual dev tooling, and genuinely useful around the house.

Here’s the site.

worklog
Launched ZUI (Zooey)

I started ZUI (Zooey), a CSS-first UI library with optional React, Astro, Solid, Svelte and Vue components. The core is just CSS with design tokens and a layered architecture — no build step required — and the framework components are thin wrappers on top for when you want them.

I’ve wanted a UI foundation of my own for a long time, one that isn’t tied to a single framework and that I can drop into anything. This is me finally building it.

worklog
xtractr

Back in 2023 I built a Cloudflare Workers page scraper. xtractr is the proper, grown-up version of that idea: extract clean, structured content from web pages with automatic short-link expansion and lightweight page-type detection.

It follows redirect chains, expands shortened links, and converts page content to clean Markdown with normalised metadata. It uses defuddle to pull out the readable content, and detects content types via Open Graph tags, JSON-LD or domain-based heuristics. It’s built for Cloudflare Workers but works in any runtime with fetch. This now powers the scraping in Otter.

worklog
strifx

Released strifx — like clsx but for strings. clsx is perfect for conditionally composing classNames, but I kept wanting the same ergonomics for any string, not just classes. So now I have it.

worklog
zed-surround

I’ve been using Zed more and more, and one thing I really missed from VS Code was being able to wrap a selection in a snippet. So I built zed-surround, a Zed extension that lets you surround selected text with code snippets — directly inspired by vscode-surround.

worklog
vite-plugin-atlas

Built vite-plugin-atlas, a convention-driven component documentation plugin for Vite + React. The idea is zero configuration — drop your components in and get docs out, no Storybook-style boilerplate. I also made a point of keeping it AI-agent friendly so tools can discover and understand the components too.

worklog
Things I've been building recently while evaluating AI agents

Ten side projects — npm packages, Raycast extensions, iOS apps and more — and what using Claude Code, Cursor and Amp on real work taught me

article
url-merge

Published url-merge, a zero-dependency URL path joiner. It’s like path.join() but for URLs, so you don’t end up with double slashes or missing ones when you stitch paths together. A tiny thing, but I was tired of reaching for the same helper in every project.

worklog
Code notes come to the site

I finally pulled my code notes into this site. They used to live on a separate 11ty site at notes.zander.wtf, but now they’re a proper content collection here — 190-odd TILs and snippets, the oldest going all the way back to 2020.

They’re fully searchable too: I wired up Algolia full-text search over the lot, which took a fair bit of debugging to get right. One less site to maintain, and everything in one place.

worklog
my-api-tools

I started my-api-tools, a growing collection of small APIs for my personal projects. Rather than spinning up a new worker every time I need an endpoint, I’m consolidating them into one place. It’s the kind of personal infrastructure that quietly powers a bunch of the other things on this site.

worklog
Add Now page

I finally added a /now page to this site. It is pretty basic right now but I hope to add more content to it soon.

Currently it contains my recent watching and listening info. The movies use my Letterboxd watch diary; the TV shows come from a Notion page; and the music comes from the Last.fm API.

The last.fm API is quite annoying because it doesn’t return artist images, so I had to use the Spotify API for those instead.

worklog
Homepage updates

I have been tweaking the styles on the site. I reduced the crop on the big “ZANDER” at the top and bottom of the site. Previously it was set dead-centre, but I found that reducing the crop (so more of the word is shown) looked a lot better.

worklog
Otter Raycast Extension

I built a Raycast extension for Otter a long time ago. It allows you to search and view recent bookmarks. It is a great companion to Otter. I have just submitted a new version to the Raycast extension store so it should be released soon for everyone to use.

The PR for the extension is here: https://github.com/raycast/extensions/pull/11066

worklog
Moving to Cloudflare

After the craziness where a Netlify customer was charged $104K because of a DDoS attack, I decided to move my personal site and as many web things to Cloudflare. Most of my sites are on Vercel and I have a few on Netlify and while I love their services, I don’t want to take any chances.

worklog
Add matching collections to Otter feed items

Bookmark feed items in Otter already show the tags associated with that item, but now I added matching collections to them as well.

Otter feed item with tags and collections

worklog
Otter Bookmark entry improvements

When adding or editing a bookmark, Otter checks the contents of the title, description and note fields to see if they match existing tags. If a match is found, it is shown below the tag input field. Since the addition of collections recently, the tag matching logic needed improveing because it would only match tags exactly, for example if the tag was web:dev and the user typed dev, it wouldn’t match. In this update I improved the matching logic to match tags that contain the user input. For example, if the user types dev, it will match web:dev and dev:ops.

Various other UI improvements were made to the app to make it better on mobile devices.

worklog
Otter database upgrade

I updated Otter’s PostgreSQL database to version 15 through the Supabase dashboard. There was an issue with the database migration, I wasn’t able to add new items anymore. I was getting this error: permission denied for table http_request_queue.

After a bit of searching, I found out that the pg_net extension was the cause of the issue. To fix I had to disable it then re-enable it in the “Database extensions” section of the Supabase dashboard.

worklog
Otter Collections

Added collections to Otter. Collections are not another entity like tags, but are created implicitly when you use a tag with a colon in, for example with the ai:openai or ai:anthropic tags the collection would be ai for both items. I use this form or grouping throughout Otter and I always wanted a simple way for collections to be displayed in the app, and now that they’re here, I can’t believe I didn’t do it sooner!

I also added a tag management page so users can rename tags. It has helped me group more tags together into collections, and I’m sure it will help others too.

This work contributed to the v2.4.1 release.

worklog
Opengraph images

I finally added Opengraph images to this site today. I used the @vercel/og package so I could automate the process as much as possible.

Here’s an example image:

The source code for the image can be found here

worklog
Otter AI titles

One of the AI features the Arc browser recently introduced renames tabs and downloaded files. It is a really small and simple thing but it made me think that Otter could use something similar. When adding new bookmarks, the titles often use very different formatting from one site to the next. This makes it hard to scan the list of bookmarks and find the one you are looking for. It would be nice if Otter could automatically rename the bookmarks to something more consistent.

I also improved the Toast component. I opted to replace the existing Toast component with a new one that is more easy to use called Sonner.

This work contributed to the v2.3.1 Otter release

worklog
Worklog

I added a worklog to my site. I’ve seen a few other folks doing this, and I think it’s a great idea. I’m going to try and keep it up to date with what I’m working on, and what I’ve been up to.

worklog
Otter Raycast OAuth

I spent a large chunk of the day trying to integrate Raycast’s OAuth flow into Otter. I got it working, but unfortunately Supabase only supports the managment APIs using OAuth apps, not the data APIs. I’m going to have to use a different approach.

I have the WIP code in two PRs:

worklog
Otter v2.1.0 release

This release added a new dashboard view to Otter that shows a some recent items and others from your past. It is intended to allow more frequent review of your past items so they are not lost in the stream. Below is a video of the new dashboard in action.

worklog
Otter v2.0.0

Updating and open sourcing my bookmarking side project

article
Otter v2.0.0 release

This release is the culmination of weeks worth of work to completely rewrite Otter . More info about this new version can be found in the announcement blog post.

worklog
Styling React 2023 edition

My approach for using PostCSS, Tailwind, cva and a few others tools to style react apps and components

article
React blogpost

Published a blog post about my approach to styling React components and apps. Read it here.

worklog
Mastodon to Supabase integration

I finished the Mastodon to Supabase Cloudflare worker. This is used to saved my toots and faved toots to Otter’s database.

The repo for it is here: github.com/mrmartineau/mastodon-to-supabase

worklog
Cloudflare scraper

I finished creating the page metadata scraper that’s built with Cloudflare Workers. It uses HTMLRewriter to parse the page and extract content. It will be integrated into Otter soon.

The repo for it is here: github.com/mrmartineau/cloudflare-worker-scraper

worklog
Reviving old music gadgets for my son

The journey to give my son access to music he loves in a offscreen, offline way

article
Otter

An introduction to my side-project

article
article
Building a table of contents component

For your Gatsby blog posts and notes

article
Announcing Code Notes

A Gatsby theme for publishing code-related notes and snippets

article
Redesigning my site... 2020 edition

Another year, another site design

article
Command line tools

A mixture of my favourite CLI tools and things that I usually forget

article
Avoid being tracked on the web

A few tips to keep you a bit more invisible online

article
Lessons learned building design systems

My rules for design system component libraries

article
Design systems at FairFX

How we are improving customer experience through shared code at FairFX

article
Jargon busting

Jargon plagues most industries. Software development is no different. Here are some of my favourites.

article
Rebel with a CORS

How to make your own simple CORS-enabled API from one that has CORS disabled

article
Introducing Design System Utils

Your helping hand to enforce your brand & design system styles with any CSS-in-JS solution

article
Creating dynamic layouts with Jekyll

How we used YAML front-matter to massively change the layout for different posts on our agency’s new website

article
Creative Technology Monthly: Disappearing interface

Issue 03: Disappearing interface with Marc Curtis - January 2017

article
Creative Technology Monthly: Motion and Animation

Issue 02: Motion and Animation — December 2016

article
Creative Technology Monthly: Chatbots

Issue 01: Bots —  November 2016

article
Updating npm packages

Exploring the myriad of options to enable you to update with ease

article
Redesigning my site... 2016 edition

The focus and techniques I used in my redesign

article
article
article
article
My process for using SVG on the web

hiDPI workflow for modern websites

article
Improving the UX of a front-end framework

How we focused on our users for the latest version of Kickoff

article
My development stack

Tools of the trade when developing for the web

article
Improve website event tracking with trak.js

Add tracking events more easily with our new library

article
The road to Kickoff version 4

What's in store for the next release of Kickoff

article
Pinboard.in Chrome Extension

An improved, more responsive design

article
article
Yeoman Generator Development Tips

Tips for those creating Yeoman generators

article
Tooling around

Are we focusing too much on the tools and not the output?

article
Speedtesting gulp.js and Grunt

Using the Kickoff framework, I compare two of the most popular client-side task runners

article
Introducing Kickoff

An overview of Kickoff for the TMW Labs blog

article
More efficient Grunt workflows

Useful tips for development with Grunt

article
Two versions of jQuery?

Why can't we serve separate versions for different browsers?

article
Live reloading preprocessed CSS using Grunt

This is all you need to get Livereload working with Grunt

article
How to fix hidden characters in password inputs in old IE

A fix for an obscure problem I found recently

article
On staying current

My recipe for staying up-to-date with web development news and trends

article
Speedy Sublime Text setup

I created a wiki to speed up Sublime Text setup, click above to find out more.

article
The Choreographic Grid

A CSS grid framework for content interdigitation

article
Customise Sublime Text 2

Tips for customising the only text editor you'll ever need.

article
Cloze test creator

I created a simple Cloze test generator

article
CSS3 'column-span'

A simple test of this handy CSS feature.

article
article
Pure CSS Icons

An experiment at creating shapes with CSS

article