§1The problem
Planning the week, shopping, batch cooking and knowing what's in the freezer were separate chores. There was no single flow from the menu to the label with the expiry date.
§2What I built
- Five screensWeek, Coach, Kitchen, Shopping and Pantry.
- CoachBuilds a menu from rules set on screen, using an LLM and recipes fetched live from a recipe service.
- ShoppingPriced shopping list, with the cart handed to the online supermarket (the final purchase is made there).
- RecipesCreated in the recipe service through the page's own API (each write checked afterwards by reading it back).
- LabelsOne label per portion on a Bluetooth printer, via D-Bus, with freeze and expiry dates.
- PantryPortion states (planned, bought, cooked, frozen).
- Local access guardAgainst requests from other sites.
§3The hard parts
5 entries- 01
Automating services with no public API
A script-driven browser, and discovering that the page uses a REST API (GET/PATCH) instead of simulating typing.
- 02
Bluetooth printer on Linux without root
The standard library wouldn't connect; I wrote only the transport, talking to BlueZ over D-Bus, and reused the library's protocol.
- 03
A printer that falls asleep
And a page confirmation that never arrives (solved by polling the state).
- 04
Readable labels
Registering the font manually so the canvas doesn't render boxes, and checking without wasting labels (PNG-only mode).
- 05
Slow asynchronous jobs and calendar rules
It always plans next week, never the current one.
§4Result
The full cycle is implemented and was rehearsed end to end in August 2026, with label printing confirmed on the printer. Last updated in October 2026.
§5Stack
- Next.js 16
- TypeScript
- SQLite (better-sqlite3)
- LLM via command line (JSON output)
- Puppeteer / automated browser
- Bluetooth LE (BlueZ / D-Bus, niimbluelib)
- @napi-rs/canvas
- pm2
- Tailscale