Loupe
Visual feedback on live websites — pins that survive deploys
A drop-in widget that pins comments to elements on a live website, then keeps them anchored through redesigns, CSS refactors, and copy edits. Relational anchoring engine, credentials that enrol browsers instead of reading them, and a dashboard built on PostgreSQL row-level security.
The Problem
Feedback on live sites is stuck between two bad options. An extension is a non-starter — a client will not install a browser extension to leave three comments, and a Chrome Web Store review sits on the critical path. And the tools that put a widget on the page anchor comments with CSS selectors, which are addresses: they say where an element lives, and they break the moment the site is redeployed. One confident pin on the wrong element teaches every reviewer to distrust all of them.
Research
I spent the most time on the one question that decides whether a visual feedback tool is trusted: when the page changes, do the pins still mean what they said? A CSS selector is an address; a relational fingerprint is a description. Markup churns constantly, copy rarely, structure somewhere in between — so I described elements by their neighbourhood and their own stable features instead of their path. Text and deliberate names are half the weight of the score, and they are the two things a class refactor never touches. I also cut the things that looked like features but parked the product in the wrong lane: no browser fingerprinting (unreliable, needs consent, and a hostile audience for a dev tool), one credential per surface, and honesty about uncertainty instead of fake confidence.
Approach
I built Loupe in layers. loupe-core is the pure engine — DOM in, data out, no rendering, no network — and it is open source, with the widget importing it rather than reimplementing it. On top, a drop-in widget that ships in one install and holds a hard 15.8KB gzipped eager budget; every feature beyond the core ships in a lazy chunk or not at all. Then a dashboard in Next.js on PostgreSQL, where every capability check lives in row-level security rather than application code, because the widget, the dashboard, and future workers all reach these tables by different paths.
Anchor by description, not selector
Nine weighted signals — text, deliberate ids, siblings, tag/role, parent, breadcrumb, media, stable classes, geometry — renormalized over whatever was actually available. A designer renaming every class in the codebase moves the score by 0.04 out of 1.0.
Three bands, and honesty in the middle
Anchored, flagged, or orphaned — never element-or-nothing. A pin that says 'this may have moved' keeps trust; a pin confidently in the wrong place destroys it. Orphaned pins go to a tray where only the thread's author re-pins them.
Credentials that enrol, not unlock
A publishable key for the page, a join code that exchanges for a revocable, device-bound membership, and a secret key that never leaves the customer's backend. Revoking a code does not touch the memberships it already created — those leave one device at a time.
The widget is the product; the extension is out
Cutting the extension removed the store review from the launch path, the broad-host-permissions conversation, and the double-pin handshake. On production, nothing renders before a credential resolves, so a customer's real users see nothing.
Challenges
A widget has no toolbar icon — so activation is a keyboard chord on production, an opt-in bubble for credentialed viewers, and a shareable URL parameter. It must be invisible until asked and fast even when it is: resolving 200 candidates costs 15 layout reads, not 400, because geometry is deferred to the top six. Every authorize-or-reject decision had to live in RLS with tests that fail the build if a comment body ever leaks to a publishable-key request.
Outcome
Loupe is shipped end to end: the anchoring engine is open source with 40 tests across eleven mutation scenarios, the widget holds its 15.8KB gzipped budget across five files, and the dashboard manages projects, keys, devices, and memberships on PostgreSQL RLS across seventeen migrations. Team mode and the Linear integration are the next two steps on the build order.
Reflection
The decisions that mattered were deletions — no extension, no fingerprinting, no full DOM snapshots. The two ideas I'd defend anywhere are honest degradation and a credential model where joining is observable and revocable per device. Both are the difference between a tool people trust and one they quietly rip out.
Let's talk
Have an ambitious idea? Let's turn it into a product.
Whether it's a fintech platform, a developer tool, or an AI-powered experience — I love helping teams ship things people enjoy using.