The blog

Building better small-business websites, out loud

Field notes, guides, and ideas on speed, AI search, design, and what actually grows a small business online.

An engineer you manage from a group chat
Field notes

An engineer you manage from a group chat

We built a coding-agent loop from Claude Code, Linear, Telegram, and git worktrees — no framework. Tickets become reviewable PRs, with a human approving every step. Day one, it shipped two merged fixes.

Read →
What actually breaks when you drop the old column
Field notes

What actually breaks when you drop the old column

Dropping our mirrored status column was easy in the model code. The real work was scripts, migration-graph conflicts, deploy timing, and NULL semantics.

Read →
The cache that never hit
Field notes

The cache that never hit

A unique job ID slipped into our cache key, giving a paid-API cache a 0% hit rate for weeks — no error, just quietly paying twice. Here's how we found it and what belongs in a cache key.

Read →
Is Meta sending your WhatsApp ad click IDs? Your database already knows
Field notes

Is Meta sending your WhatsApp ad click IDs? Your database already knows

We thought our Click-to-WhatsApp ads weren't sending ctwa_clid. One query on our own leads table showed the exact day it switched on.

Read →
When your AI agent's memory overrules the user
Field notes

When your AI agent's memory overrules the user

Our build agent kept a persistent memory. A customer asked to change a dull background colour; the agent recalled an older "no bright colours" preference and ignored them. Here's why that happens, and what we changed.

Read →
How we isolate Claude Code skills per client (it wasn't --setting-sources)
Field notes

How we isolate Claude Code skills per client (it wasn't --setting-sources)

We assumed --setting-sources project isolated Claude Code skills per client. It only scopes settings, not skill discovery. The real boundary came from per-workspace git roots and a fail-closed allowlist copy step.

Read →
How to move a live status field onto a new model without a big-bang migration
Field notes

How to move a live status field onto a new model without a big-bang migration

We moved lifecycle state to a new model without rewriting every query. The trick was keeping the old status column as a mirror and migrating the writers first.

Read →
Your WhatsApp ads convert where your analytics can't look
Field notes

Your WhatsApp ads convert where your analytics can't look

If your ads open a WhatsApp chat, the conversion happens off your site and won't show in standard web analytics. Here's what actually links ad spend to sales.

Read →
Your git log is already your changelog
Field notes

Your git log is already your changelog

Hand-written changelogs create merge conflicts because every branch edits the same lines. We stopped maintaining one and generate it from commit messages instead.

Read →