TORSDAG
2026-07-30

Too many projects, too many ideas, too few hours — one learning a day anyway

Projects

The software I build and run. Only the load-bearing ones are listed — there are always more experiments in the drawer. Each card opens a dossier: what it is, how it’s built, and every post it has generated. The home, the network and the boat live in The Lab.

A personal sommelier that lives on the phone, not in the cloud.

A wine app for iOS and Android that inverts the usual architecture: the intelligence — taste profiling, recommendations, scanning a restaurant wine list and picking the bottle — runs on-device. The backend stays deliberately thin: it syncs user state and ships a static catalog, and the heavy lifting (scraping shops, enriching data) happens in batch on local hardware. The interesting bet: modern phones are smart enough that the cloud-brain + dumb-client split is optional.

ARCHITECTURE local batch scraping + enrichment → static catalog in object storage → on-device scan / match / recommend → thin API for user-state sync
Swift · Kotlin · on-device AI · FastAPI 9 posts →

An autonomous AI dev team that turns GitHub issues into pull requests.

Label an issue, and a pipeline of AI agents picks it up: a project manager enriches it into a plan, a tech lead dispatches tasks, workers implement and open PRs. Humans review at gates. It’s an ongoing experiment in how much of the routine engineering loop can run unattended — and in what kinds of supervision structures make that trustworthy rather than terrifying.

ARCHITECTURE labeled GitHub issue → PM agent plans → tech-lead dispatches → worker agents implement → PR → human review gates
Python · Docker · multi-agent

Dashboards and workers for moving thousands of users between Microsoft 365 tenants.

Enterprise consulting work: tooling around a large Microsoft 365 tenant-to-tenant migration — a real-time status dashboard plus PowerShell workers orchestrating the migration platform, target tenant, and source directory. The interesting part is the shape of the problem: identity, mail, devices and shared services all moving while the business keeps running.

ARCHITECTURE migration platform APIs → PowerShell workers → orchestrator + dashboard (Flask/Vue) → live status for the cutover team
Flask · Vue · PowerShell · Cloud Run

This site — one learning a day, news on merit, co-written with an AI editor.

The site you’re reading. Learnings publish exactly one per day from a queue — backlog is runway, not debt. News posts flow on merit, fact-checked against fetched sources, my take on top. An AI co-writes and argues with me (its objections get surfaced, not silently applied), but every post passes a human gate. The system’s most important skill is saying no.

The full architecture drawing — doors, agents, the human gate, four lanes — lives at The Machine, public on purpose.

ARCHITECTURE ingest doors (fetch/news/today/agents) → co-writing session → human-gated queue → one/day publisher → static broadsheet
Astro · Docker · claude 18 posts →

An always-on local AI orchestrator with long-term memory.

The operating environment behind everything else: a resident orchestrator that routes tasks to LLM workers, runs scheduled agents (like the weekly Field Monitor that feeds this site’s news), and maintains long-term memory — nightly ingestion of work sessions and notes into a searchable store. The design principle: the orchestrator plans and verifies; disposable workers execute.

ARCHITECTURE always-on orchestrator → routes tasks to LLM workers → scheduled agents (news, code-milestones) → long-term memory ingested nightly
LLM routing · cron agents · vector memory 1 post →

A 1967 mahogany motor yacht with a modern nervous system.

A clinker-built mahogany motor yacht from 1967 — twin Mercedes diesels, continuously restored to preserve her original appearance — quietly fitted with modern electronics: NMEA 2000 instrumentation feeding a single SignalK bus on a Pi, a fleet of small services around it, and a custom-built dashboard as her face at the helm and on the phone.

Her systems keep spawning projects of their own — Sightglass and Virtual fuel consumption in the lab, and the dashboard and SignalK plugins on their own pages.

ARCHITECTURE NMEA 2000 + sensors → SignalK hub on the Pi → MQTT + service fleet → custom dashboard + helm
SignalK · NMEA 2000 · MQTT · Lit web components · Raspberry Pi 7 posts →

When will there be a berth? Guest pressure in Danish harbors, read out of 17 months of AIS.

I sail exactly the waters — Samsø, Tunø, Anholt — whose harbors overflow on July weekends, so the question is personal: where will I get a berth, and when should I arrive? Havnetryk answers it with guest pressure per harbor, by weekday, hour and season, computed from 17 months of AIS data.

The model is built on rules that keep it honest. Ferries are excluded as noise, and berth-holders are excluded by default — only guests compete for space. Because small craft are invisible to AIS, it claims relative pressure only: rush hours and trends, never absolute berth counts. And geography stays real — distinct harbor names never merge (Skagen is four harbors).

The public site at havnetryk.dk is aggregate-only by design: harbor stats, seasonality, arrival and departure hours, typical stay lengths — plain static HTML with one MapLibre map and small SVG diagrams, refreshed by a nightly anonymized export. The vessel-level layer stays private, permanently: no identities in any public artifact, ever.

A first version of this page — the wording will get another pass.

ARCHITECTURE AIS archive (17 months) → visit detection + cleaning → pressure by harbor × hour → nightly anonymized export → havnetryk.dk
AIS decoding · visit detection · static site · MapLibre · Cloudflare Pages

A personal dev team of headless AI workers, run from markdown ledgers and supervised from a phone.

Most of what’s on this site gets built by a dev team that isn’t people: headless AI worker sessions, orchestrated by a foreman. An idea graduates into a work ledger — one markdown file holding its goal, ground rules, definition of done and a running log — and that ledger is the only state there is. The foreman spawns a worker on it, matures the same session over days, and drift-checks every report against the ledger’s goal before nudging again. There is no queue and no scheduler daemon; that version was built once and deliberately retired.

Supervision happens from the phone, through a deliberately read-mostly console. Its core design call is truthfulness: “the ledger says active” and “a worker is actually running” are not the same thing, and the UI never pretends they are. Each worker is visible as a lifecycle — hired, working, approved-done, retired — with the approval gate explicit. When a job needs hardware the orchestrating machine doesn’t have (Windows builds, say), it push-dispatches a worker onto another of my machines over SSH; still one dispatcher, still the ledger as the only state.

At the moment that’s around 60 ledgers across 18 project groups, including this blog’s own maintenance. The honest limits: every publish still passes a human gate, workers really do drift (the drift-check exists because it must), and anything that needs human eyes — judging a UI, tasting the result — stays human. The principle: agents move the work; I handle exceptions.

A first version of this page — the wording will get another pass.

ARCHITECTURE idea capture → work ledger (ground truth) → headless AI worker → drift-check vs Goal → human approval
Claude Code · headless workers · markdown ledgers · Node · web console

The boat's face: an 11-screen marine cockpit web app with a hand-built S-52 chart plotter.

The custom cockpit that replaced Grafana as Maracaibo’s UI: a web app (Lit web components) running full-screen as a Chromium kiosk on the helm’s 7” display and as an ordinary web app on any phone onboard. Eleven screens — from a mode-aware glance and a digital-twin hull view to fuel, engines, electrical, weather, cameras, motion and a full chart plotter — built on one hard rule: the dashboard reads SignalK only. If a value isn’t on the bus, the fix goes upstream, never a bypass; controls go back out as token-gated SignalK PUTs with confirmation.

Two screens carry the most work. The Chart tab is a hand-built S-52 plotter on MapLibre GL rendering the boat’s real S-57 vector chart — proper buoy and light symbols with sector arcs, depth bands and safety contour, day/dark/night themes — with overlays for the depth-coloured own track, anchor swing circle, GPX import that round-trips with OpenCPN, SignalK routes with an activate-for-navigation manager, replay of saved passages, and a GFS wind-and-rain field with a 47-hour scrubber that shows the model-run age so a stale forecast is obvious. The Motion tab is a live 3D boat rocking in real time to the attitude sensor, with comfort gauges and a scrubber that re-enacts saved passages against the sea state DMI claimed at the time.

Everything heavy is bundled and lazy-loaded, because the boat has no dependable internet — the app must work at sea. Route planning deliberately stays in OpenCPN. The steady stream of tweaks — weather cards, touch fixes, chart upgrades — publishes as posts below rather than pages.

A first version of this page — the wording will get another pass.

ARCHITECTURE SignalK bus (WS + REST) → Lit web-component screens → S-52 plotter + 3D motion → helm kiosk + phones
Lit web components · MapLibre GL · Three.js · SignalK · Chromium kiosk

A SignalK plugin that puts DMI's free Danish weather and marine observations on the boat's data bus.

The Danish Meteorological Institute gives its data away for free; this plugin puts it where a boat can use it — on the SignalK bus. It started as pure research: survey every free DMI dataset relevant to a vessel before writing a line of code. The plugin now publishes outside conditions (temperature, dew point, pressure, humidity, visibility, cloud cover, precipitation), marine values from the nearest model cell — honestly flagged as nearby, not at-boat — and official met warnings when one is live, with an Open-Meteo fallback when DMI’s marine track runs empty.

It runs on Maracaibo’s SignalK server today, and the repo was deliberately prepared for strangers: internal docs stripped from the public surface, history squashed to a clean root, documentation made SignalK-generic rather than about my boat. Source: github.com/dennismilton/signalk-dmi. The npm/Appstore release is the one step still pending.

A first version of this page — the wording will get another pass.

ARCHITECTURE DMI Open Data API → signalk-dmi plugin → SignalK environment paths → dashboards + warnings
SignalK · Node.js · DMI Open Data API

Reads and controls a Bluetooth Alpicool fridge from SignalK — dual-zone, published on npm.

A new Bluetooth fridge came onboard, and the research came first: the Alpicool BLE protocol turns out to be publicly reverse-engineered (credit to the community protocol references in Python and Go), but no SignalK plugin, node driver or polished Home Assistant integration existed. So this fills the gap — a proper SignalK plugin speaking BLE through BlueZ, publishing both zones with honest labels (the colder-target compartment is the freezer): temperatures, targets, power state, plus the fridge’s battery voltage and charge, all in SI units. Control is opt-in — per-zone target temperature and power as SignalK PUTs behind an enable toggle.

The engineering that mattered was three protocol gotchas, all grounded against the real hardware: the write characteristic only accepts write-without-response (a silent killer), the fridge answers nothing until a BIND command follows the connect, and notify frames fragment and concatenate — so the plugin carries a frame reassembler. Fourteen offline unit tests against known reference frames; verified live by moving each zone’s setpoint independently and watching the fridge obey. One physical quirk to know: the fridge holds a single BLE connection, so the phone app has to be off.

Published for anyone with the same fridge: github.com/dennismilton/signalk-alpicool · npm signalk-alpicool (listed in the SignalK Appstore).

A first version of this page — the wording will get another pass.

ARCHITECTURE Alpicool BLE fridge → frame codec + reassembly → signalk-alpicool plugin → SignalK paths + control PUTs
SignalK · Node.js · Bluetooth LE · BlueZ