← All posts

June 19 — the day Loupe got its name, its face, and its front door

This was one of those days where I touched almost every part of the project at once. No single big feature — more like five threads braided together, each going recon → build → verify → ship. Writing it down so I remember what actually moved.

Built / shipped

Renamed the project to Loupe, for real. The two repos finally got the names I'd been calling them in my head: the app became loupe, the pipeline became loupe-pipeline. This had been sitting as a planning doc for a while — I'd even talked myself out of a physical rename earlier in favor of symlinks. This time I reversed that and just did it: stop everything, move the directories, fix the hardcoded sibling-path literal in seven app files, restart, verify the app still serves live data from both database layers. It does. Felt good to stop apologizing for the old names.

Map and Cutting Room went live. Promoted two things from buried sub-tabs to real top-level pages. The Map draws a vendored, public-domain basemap (no external tiles, no API calls at all) under ~48k geotagged points with clustering and a time scrubber; selecting a place opens a preview card before you commit to reviewing it. The Cutting Room turns the old "candidates" filter toggle into a designed page — six rule piles with real reclaim numbers, contact-sheet strips, a "review all" path into the existing flow.

The People view. This is the one I'm most excited about. The pipeline had already detected faces across the library and stored their embeddings, but nothing in the app ever read them — totally greenfield. I built a Faces module that lazy-loads all the embeddings into one normalized numpy matrix on first request and computes "find more faces like this" with a single cosine matmul (no FAISS needed at this scale). Phase 1 was read-only suggestions; Phase 2 added the confirm/reject/snowball loop — the first writes the app makes to that database. Confirm a face, it becomes an anchor, the next round of suggestions gets better. I validated it by rendering montages of my own top matches: the top ~100 are unambiguously me, and quality only starts drifting around a cosine of 0.87.

A real brand. Redrew the identity around a "loupe-cup" lens-on-film mark and pushed it everywhere — the animated loading spinner, the static header mark, and a self-contained SVG favicon plus a full raster icon set (16 through 512). Same icons shipped to the live davidgomez.cc marketing site so the tab looks identical in both places. Also changed the page title to the tagline: Loupe — look · cull · loop. And I wrote up the whole CSS/SVG design system as a dossier so the visual language is documented, not just vibes.

The onboarding front door. Started the setup flow that picks where your library lives and, at altitude, handles connecting to your photo library / signing in. The whole thing is reachable only on my home network, never from the public side. (More on what I'm deliberately not detailing below.)

Problems & fixes

Decisions

Learned

Still open / next