← All posts

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

The thing that clicked today: I touched almost every part of the project at once, no single big feature, more like five threads braided together, each going recon then build then verify then 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 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 bundled, public-domain basemap (no external map 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 an embedding for each one (a list of numbers that captures what the face looks like, so two embeddings can be compared), but nothing in the app ever read them. Totally new ground. The Faces module loads every embedding into memory the first time it is asked, and "find more faces like this" is a similarity comparison of one face against all of them. At this library size that is a single matrix operation; it did not need a dedicated search library. 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 similarity 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 icon set (16 through 512 pixels). 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 a high level, 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