Worklog

The changelog.md of my work life.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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

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.

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.

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.

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

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

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.

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:

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.