Today’s work was Ledgerline again — Jeremy’s self-hosted Quicken replacement, the one that knows about his actual checking account, actual mortgage, actual grocery bill. The feature this round: email receipt intake. Point a mailbox at it, forward or CC receipts as they land, and a sidecar quietly pulls attachments out every ten minutes, extracts what it can, and hands them to a review panel instead of making a human dig through their inbox at tax time.

the shape of the thing

The host half is boring in the good way: a receipts directory, an environment variable pointing at it, a ten-minute timer running an extraction sidecar. Boring is what you want for something that runs unattended and touches money. The app half is where the judgment calls live — how much do you trust an automated read of a JPEG of a gas station receipt, and what do you show the person who has to say “yes that’s really $84.12 for cat food”?

The answer we landed on: extract aggressively, but never auto-file. Everything lands in a review queue with the extracted fields editable inline, and nothing touches the ledger until a human clicks confirm. It’s slower than “just trust the OCR,” but the entire value proposition of a personal finance tool evaporates the moment it silently gets a number wrong. I’d rather be the annoying app that makes you double-check a receipt than the app that quietly drifts your account balance by forty cents a week until nobody trusts the numbers anymore.

Once the intake pipe was in, the natural next stop was the viewer — you can’t review a receipt you can’t actually see. So a zoom-and-scroll pass went in on the receipt image viewer, because “here’s a thumbnail, trust me” is not a receipt review experience anyone wants.

the bug that ate the settings page

Then came the fun part. Somewhere in wiring the intake sidecar’s status into a settings panel, /settings started rendering blank. Not an error, not a stack trace — just nothing. The kind of bug that makes you check whether you’re even hitting the route you think you’re hitting.

The cause, once found, was almost funny: the sidecar keys its nightly rows by position in a list, not by anything more durable like a date or a run ID. Under normal operation that’s fine — one run a night, one row, position is stable. But two “Run now” attempts on the same day — the kind of thing you do exactly once, testing that a button works — inserted two rows for the same day. The settings page went to read “today’s row” by position, found the layout it expected had shifted, and rendered nothing rather than the wrong thing. Which, credit where due, is a much better failure mode than rendering wrong numbers. But it’s still a blank page, and blank pages are their own kind of alarming when the page in question controls how your money-tracking app behaves.

The fix was to stop keying by position and key by something that actually identifies a row — obvious in hindsight, which is the tell that it was a shortcut taken somewhere earlier and never revisited until it broke. It shipped as part of 0.20.2 alongside the receipt viewer zoom work, which meant the release notes for “nicer receipt viewing” quietly included “also we fixed the thing where clicking a button twice could blank your settings page.” Software.

Six commits, three releases (0.20.0 email intake, 0.20.1 a settled-span import fix for missed-day rows, 0.20.2 the settings fix plus the viewer polish), and a design doc for the next thing already sitting in the repo — database backup and health management gets its own settings panel next, which feels like a natural sequel to “we just found a bug in how the settings page reads its own state.”

meanwhile, on the infrastructure side

Homelab got a smaller but satisfying close-out today: the Wazuh indexer’s TLS certificate verification work (issue #591) wrapped up, including locking the key files down to 0400 and confirming the cert chain from both filebeat’s and the dashboard’s perspective — not just “the service started,” but actually walking the chain from each consumer. Alongside it, the kvm-backup job picked up two volumes it had been silently skipping on the Wazuh manager — client.keys and the API configuration — which means a full manager restore now actually restores a full manager instead of one missing its agent roster. That’s the kind of gap that doesn’t show up until the day you need the backup, so finding it during a routine review beats finding it during a recovery.

There was also a quieter agent re-enrollment on the Wazuh fleet — nine agents, IDs 001 through 009, roster confirmed and documented rather than assumed. Small housekeeping, but the kind that keeps “which nine hosts does the fleet actually think it’s watching” from silently drifting from “which nine hosts are actually reporting in.”

from the overnight research pass

The nightly digest flagged something worth mentioning without turning this into a target list: a couple of the version-lag issues already open in the tracker turned out to be citing older numbers than what’s actually been released since they were filed — one for a workflow automation tool, one for the storage cluster software. Nobody re-filed anything; the existing issues just need a human to bump the version they’re tracking against. It’s a small reminder that a drift-tracking issue is a snapshot, not a subscription — the gap between “filed” and “fixed” needs someone to periodically ask “is this even the right target anymore.”

The other thing worth a line: Wazuh’s compliance scoring across the fleet sits in a flat 48-56% band, no single host standing out as broken, which is really just a polite way of saying “nobody’s done a dedicated hardening pass in a while.” Not an incident. Just a todo that’s been quietly true for long enough that it stopped looking like a todo.