Marketplace Listing Helper: A list that lives in the browser - Part 3
~7 min read
The extension needs to remember what I'm selling. With no server allowed, the list has to live in the browser, and it has to move to and from a spreadsheet, because that is where I keep my stock. This post is the data model, and the small decisions that keep a CSV and a browser store from fighting each other.
Where it lives
Everything is in chrome.storage.local, under two keys:
* mlh_data is { headers, rows }: the column names in order, and one object per item.
* mlh_meta hold
Central login service: A two-step login wizard - Part 8
~12 min read
Part 7 closed with "that's the story to date". It wasn't quite. The login page still had one awkward detail from the very first version: a single form with three fields, the last of them labelled "2FA code (only if you have it enabled)". Everyone, including people who have never turned on two-factor, saw a box asking about something that didn't apply to them.
This post turns that form into a small wizard. Step one asks for an email and password. Step two, a code from your authenti
Childcare calculator: Spreading hours across the week, and the partial-week problem - Part 4
~9 min read
Splitting free hours gives each childminder a weekly allocation. This post is about turning that into the day-by-day numbers on the calendar, adding a weekly breakdown, and fixing the most interesting bug in the project: a month rarely starts on a Monday.
Free hours are weekly, the calendar is daily
The first version applied each week's free hours chronologically: Monday's hours were free until the allowance ran out, then the rest were paid. The totals were right, but the calenda
Personal finance aggregator: Seeing everything in one place - Part 11
~9 min read
By post 8 the app could pull balances and transactions from every linked bank. What it couldn't do was make that data legible - or hold the parts of my financial picture that no bank API will ever give me, like a car loan. This post is a run of smaller commits, spread over 28-29 September, that were each about the same thing: making the Accounts and Home pages answer "what do I actually have, and what do I owe?" without squinting.
It's less a single design story than five related f
House-move inventory: Money, currencies and an approximate euro - Part 3
~7 min read
Every item in the inventory can have a value, and values are the part of this project where a quiet bug does the most harm: the number ends up on a customs declaration and in an insurance conversation. So the money rules were written down before the first item was added, and they have barely changed since.
The rules
From CLAUDE.md:
* Money is Decimal end to end, and a string on the wire ("45.00"). Never float - the service actively rejects one.
* New items default to GBP.
* V
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