Marketplace Listing Helper: Filling in Facebook's form - Part 2
~7 min read
Filling in a form sounds like the easy half of the job. It's the half that breaks. Facebook's Create listing form is built from generated markup with no stable ids, its inputs are controlled by React, and its dropdowns are custom widgets. This post is about how the extension copes, and about the one rule that keeps it honest when it can't.
Finding a field without an id
There's nothing like #title to select. What is stable is the text a person reads, so findField() looks fields up
Childcare calculator: Splitting free hours across childminders - Part 3
~8 min read
The part of this calculator that actually needed thinking about is free hours. A funded entitlement is a number of hours per week, and it isn't tied to a particular childminder. If you use two childminders at different rates, you choose where those free hours land, and the choice changes the monthly bill quite a lot. This post is about the four strategies and the one rule that keeps them honest.
Why it matters
Take the default setup in the app: Childminder A at £5.50/hr for four
Central login service: Polish, and creating users from the browser - Part 7
~22 min read
The last two commits to date are the least dramatic and, in a way, the most revealing about what a project looks like once the hard problems are solved. One (feat: make UI look nicer) is almost entirely CSS. The other (feat: adding new user) fills the last hole in the admin story: until now, the only way to create an account was to SSH into the container and run a command-line tool.
This final part covers both, and then steps back to take stock of the whole series: what the finish
Personal finance aggregator: One storage tree per user - Part 10
~8 min read
Once identity-system was putting a user id on every request, one assumption baked into the app from post 4 became visibly wrong. Everything - the SQLite database, the OAuth refresh tokens, the fetched JSON - lived in one shared storage/ directory, because there had only ever been one person. The moment a second account could log in, they'd see the first account's banks.
This post is about how I fixed that, and why I picked the less clever of two obvious designs. It's three commits
House-move inventory: A core that knows nothing about HTTP - Part 2
~8 min read
Post 1 described rooms, boxes and items. This post is about where the rules about them live, because that decision has paid for itself more often than any other in the project.
The rule is one sentence in CLAUDE.md: inventory domain logic lives only in inventory.core. The API and the CLIs are thin translation layers - no rules, no validation logic, no aggregation. If a behaviour matters (quantity must be at least 1, a box that isn't going can have a reason, clones copy their source
Marketplace Listing Helper: A helper that never presses Publish - Part 1
~6 min read
I had a few dozen things to sell, mostly Warhammer models, and a spreadsheet describing them. Facebook Marketplace wanted each one typed into a form, one at a time: title, price, category, condition, description, photos. None of it is hard. All of it is repetitive, and repetition is exactly what a computer should be doing for me. So I started building a Chrome extension to do the typing, and the first decision was about what it must not do.
The rule everything else hangs off
The
Childcare calculator: From two childminders to four - Part 2
~7 min read
Last time the calculator had exactly two childminders, written into the markup as "A" and "B". That matched my own situation, but it's a poor assumption for a tool other people might use. This post covers the commit that replaced it with one to four childminders, and what that did to the code.
The assumption was everywhere
Two childminders sounds like a small thing to hardcode. In the first version it showed up in:
* the markup, as two pairs of name and rate inputs
* the sched
Central login service: Moving configuration into the database - Part 6
~25 min read
By the end of Part 5, the service was doing real work: apps behind the proxy used it for forward-auth, and Jenkins signed in through it with OpenID Connect. What it wasn't was pleasant to operate, and the reason was the same in both of its configurable parts.
There were two pieces of configuration that changed over time:
* The apps directory: the cards on the landing page, held in apps.json.
* The OIDC client registry: the relying parties allowed to sign users in, held in oidc_
Personal finance aggregator: Handing authentication to identity-system - Part 9
~8 min read
Post 6 ended with a login system I was reasonably pleased with: Argon2id passwords, opaque server-side sessions, TOTP two-factor with recovery codes, a password-change form that borrowed login() to verify the current password. It had also, by the end of that post, hit two gaps in the library underneath it - no way to update a user's name or email, and no built-in current-password check - that I'd worked around rather than fixed.
The next day I deleted all of it. This post is about
House-move inventory: Why a spreadsheet wasn't enough - Part 1
~7 min read
We're moving from the UK to France, and the removals company, the French customs form and my own sanity all want the same thing: a list of what is in every box. Not a rough list. Which box is the kettle in? Did every box that left the house actually arrive? What's the declared value of the kitchen? Which boxes are we not taking after all?
The obvious tool is a spreadsheet, and I started with one. This post is about why I stopped, what I built instead, and the few decisions in the f
Childcare calculator: Why a static page, and why not a spreadsheet - Part 1
~6 min read
Childcare costs are one of those bills that look simple until you try to predict them. A childminder charges an hourly rate. You book some days. The government funds some of the hours. Bank holidays don't count. And somehow the number on the invoice is never the number you guessed.
I wanted a way to type in my setup and see the month's cost, day by day, before the invoice arrived. This post is about the first decision: what to build it with, and what the first version actually did.
Central login service: Making Jenkins sign in - Part 5
~20 min read
Part 4 ended with an OpenID Connect provider that passed its test suite, covered the whole Authorization Code flow, required PKCE, hashed its client secrets, and was, by the specification as I'd read it, correct.
Then I pointed Jenkins at it, and the next half hour produced three commits with titles like fix jenkins login, fixing the fix further and Adding preffered username. This part is about those three commits. They're short (the three of them touch well under a hundred lines