← All posts

June 16 - a kernel reboot, two apps becoming one, and an enrichment layer pulled from Apple

The thing that clicked today: two sibling photo-review experiments stopped being two things and became one, Loupe, running as a real managed service with its own brand, its own cloud backup, and an enrichment layer pulled straight out of Apple Photos. It opened with a recovery I didn't ask for and ended with a lot of threads tied off. Here's the whole day.

Built / shipped

Problems & fixes

The box had silently rebooted. I came back to a dropped connection and a dead stack and assumed the worst: a crash, a storage fault, a power event. It was none of those. The reboot log plus a bumped kernel version told the real story: an unattended security upgrade had cleanly rebooted the machine overnight, and because the whole stack was hand-started with no auto-start, everything died with it. Checking reboot history before assuming a fault saved me an hour of chasing a phantom. Everything on disk was intact; I just had to restart it. This is the exact pain that motivated making Loupe a managed service later the same day.

The thumbnail generator was failing 100%, and it was OOM. It wasn't a code bug in the obvious sense. It was trying to decode up to 200 large HEIC images at once on a machine with only about 8 GB of RAM, and the box ran out of memory. The fix was to cap concurrency hard (twelve image workers) and split the work into an image phase and a video phase. That cap is now load-bearing. Every path that touches decoding respects it, and I've written down: never raise it.

Two silent write-path failures. Both the OpenCV image writer and FFmpeg infer the output format from the file extension. Writing to a temp name ending in .jpg.tmp makes both of them silently fail: no error, no file. The fix: encode to a JPEG in memory and write the bytes, or write to a …tmp.jpg name and rename afterward.

The public site was returning 502. The fix was binding the app to all interfaces and pointing the tunnel's ingress at the right origin. The tunnel runs on a different host than the app, so pointing it at localhost could never have worked.

Summaries kept naming home after a nearby business. The venue resolver was labeling clusters by whatever shop sat a few meters away, so a quiet day at home came out labeled after some random nearby café or restaurant. And an earlier "discount far outliers" heuristic had erased a real trip: a genuine drive out to a ranch got filtered as noise. Two lessons collided here: an erased real place is invisible and unrecoverable, while a wrong label is at least catchable. So I ripped the outlier discount out entirely and kept only a minimum-cluster-size filter: under-filter, never over-filter. For home specifically, I suppress to the suburb name plus a distance gate, rather than fighting it with radius tuning that the geometry didn't support anyway.

The location provider rejected the call. The legacy geocoding API returned a flat denial for a new project. The newer nearby-search API worked, and skipping sub-feature points of interest (bathrooms, parking lots) let the real venue resolve.

Decisions

Learned

Still open / next