SØNDAG
2026-09-13

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 10 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 19 posts →

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 15 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.

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 havnetryk.dk ↗ 1 post →

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 70 ledgers across more than twenty 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.

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 1 post →

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.

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 read-only mailbox audit that finds every recurring payment nobody re-decided to keep.

Signing up is a decision made once; continuing to pay is a decision made never. Spendwatch runs a read-only audit over a mailbox — the one place that remembers every sign-up — and hands the standing costs back.

A payment counts as recurring on a regular interval, with explicit renewal language, or as a sequential invoice series. Pay-per-use receipts don’t count.

The harder part is the blind spots: billers with no receipts, only price-change notices; attachments unreadable through the connector. The report states what it didn’t search. Read-only is enforced by never granting write tools, not by instruction.

ARCHITECTURE mailbox (read-only) → recurring-payment detection → blind-spot self-test → standing-cost report
mail connector · read-only audit · pattern detection 1 post →

A private club app for iOS — one app, clubs inside, each with its own name, mark and colours.

Sodalis is one iOS app with clubs inside it. A club is the usual committee load in one place: members, events, tasks, documents, decisions. Each club carries its own name, mark and colours: one paper template throughout, where the colour is the ink and not the page. No emblem, and the club gets its initial. Clubs are closed — only members see the content.

Nobody types a password. The ways in are an e-mail one-time code, Sign in with Apple, Google, or a passkey. Clubs are self-served — create one, invite by code.

SwiftUI over a Kotlin Multiplatform core; the server is Cloudflare Workers with D1. Auth0 carries the passwordless codes; the passkeys are my own WebAuthn, outside it. On TestFlight now, headed for the App Store.

ARCHITECTURE SwiftUI + KMP shared core → Cloudflare Workers + D1 → Auth0 codes + own WebAuthn passkeys → tenants as idempotent scripts
SwiftUI · Kotlin Multiplatform · Cloudflare Workers · D1 · Auth0 · WebAuthn sodalisapp.com ↗

A one-page calling card: what the work is, in prose, with no client names and no timeline.

A CV answers “where has he been”. This page answers “what does he do”, so it carries sectors instead of client names, and no dates at all. The full CV goes out on request.

Four candidate designs were built side by side and one was chosen; the rest are in the history. The renderer refuses to return a page whose section parsed empty, because a card that quietly drops a section still looks fine.

The other three domains redirect here, and the contact address is forwarded rather than hosted — so the page invites mail without publishing a mailbox.

ARCHITECTURE content.md → render gate → static page → Cloudflare Pages → dennismilton.com
markdown source · static render · Cloudflare Pages · Email Routing dennismilton.com ↗ 1 post →