@loupekit/sdk
v0.6.0
Published
Embeddable visual-feedback layer: pin comments to the live UI and hand them to Claude.
Maintainers
Readme
Overview
Traditional feedback — "the revenue card looks off on the dashboard" — loses the one thing an engineer needs: which element, in what state, on which page. Loupe captures all of it at the moment of the comment, so the feedback stays actionable even after the UI changes underneath it.
Table of contents
- Features
- Install
- Quick start
- Re-anchoring across redeploys
- Configuration
- Redaction
- Auth model
- Storage seam
- The loop
- Related packages
Features
| | |
| --- | --- |
| 🎯 Click-to-comment inspector | Hover-highlight any element, click to pin a comment. |
| 💬 Free comments | Drop a page-level note anywhere with the Note mode — no element, no screenshot. |
| ▭ Free-region screenshots | Drag a free-size box, screenshot exactly that area, comment on it. The region anchors to the element under its center, so it tracks responsive reflow and scrolling. |
| 🧲 Dockable control | A DevTools-style panel: dock it to the left / right / bottom edge (which pushes your page over so it's never covered) or float it as a movable, resizable window. Light/dark theme, collapses to a small ◎ launcher, and becomes a bottom sheet on mobile. Position & theme persist. |
| 🔁 Redeploy-surviving re-anchoring | A multi-signal fingerprint (stable id/testid, CSS path, XPath, text, attributes, position) re-locates the element on the current page; if it can't, the pin detaches instead of pointing at the wrong thing. |
| 📸 Screenshot capture | [data-loupe-redact] regions are painted over before any pixels leave the browser. |
| 🧩 Shadow-DOM isolation | The widget's CSS never leaks into your page and vice-versa. |
| 🔌 Pluggable storage | Talks to the Loupe backend, or persists to localStorage for offline/demo use. |
| 🤖 Claude-ready | Every comment carries the element HTML + computed styles + screenshot Claude Code needs to make the fix. |
Install
npm i @loupekit/sdkAlso mirrored to GitHub Packages as
@mohamed-ashraf-elsaed/sdk— add@mohamed-ashraf-elsaed:registry=https://npm.pkg.github.comto your.npmrcto install from there.
Quick start
import { init } from "@loupekit/sdk";
init({
projectKey: "pk_live_yourkey",
user: { id: "u_92", name: "Sara (PM)", email: "[email protected]" },
apiBase: "https://loupe.yourbackend.com",
// HMAC-SHA256(user.id, PROJECT_SECRET), computed on your server (see Auth model).
userHmac: "decb2c…",
});A dockable control panel appears with Inspect, Note (a free page-level comment),
Region, and the comment list. Use the header's dock controls to dock it left / right /
bottom (which pushes your page over) or float it, toggle light/dark, or close it to a small
◎ launcher. Call destroy() to tear it down. init() is idempotent — safe to call more
than once. Pass label to change the brand name shown in the header.
Offline mode (no backend)
Omit apiBase and comments persist to localStorage — great for demos and local dev:
init({ projectKey: "pk_demo", user: { id: "u_1", name: "You" } });Re-anchoring across redeploys
The crown jewel. A comment records a multi-signal fingerprint of its target. On the next page load — even after the UI is rebuilt, relabeled, or reordered — Loupe re-locates the element and moves the pin to it. Below, the "Revenue" card was relabeled to "Total Revenue" and reordered, yet the pin follows it:
Configuration
init(config: LoupeConfig):
| Option | Type | Description |
| --- | --- | --- |
| projectKey | string (required) | Public project key issued by the backend. |
| user | { id, name, email? } (required) | The already-authenticated host-app user. |
| apiBase | string | Backend base URL. Omit → localStorage (offline mode). |
| userHmac | string | HMAC-SHA256(user.id, PROJECT_SECRET) computed server-side. Required in production for writes. |
| autoOpen | boolean | Start with the inspector already active. |
| captureScreenshot | (el: Element) => Promise<string \| undefined> | Override element screenshot capture (the extension backs this with captureVisibleTab). |
| captureRegion | (rect: RegionRect) => Promise<string \| undefined> | Override free-region capture. rect is in viewport coordinates. |
| headers | Record<string, string> | Extra headers merged into every backend request (e.g. a CSRF token). |
| credentials | RequestCredentials | credentials mode for backend requests — set "include" for cross-origin cookie auth. |
Redaction
Any element marked data-loupe-redact is painted over in screenshots before the pixels
ever leave the browser — use it on PII, secrets, or anything sensitive:
<input data-loupe-redact value="[email protected]" />Auth model
Each project has a secret. Writes require X-Loupe-User + X-Loupe-Hmac
(= HMAC-SHA256(userId, PROJECT_SECRET)), which your server computes and injects into the
page — users can't spoof identity. The dashboard and MCP server authenticate as admin with
the raw secret.
Storage seam
Anything implementing StorageAdapter can back the widget — swap in your own transport:
interface StorageAdapter {
list(projectKey: string, url: string): Promise<Comment[]>;
save(comment: Comment): Promise<Comment>;
update(id: string, patch: Partial<Comment>): Promise<void>;
remove(id: string): Promise<void>;
}The loop
SDK (in your product) → backend API → Postgres + object storage → dashboard (human triage) and MCP server (Claude reads it) → status flows back.
Related packages
| Package | Description |
| --- | --- |
| @loupekit/mcp | MCP server that hands the comments to Claude Code. |
| @loupekit/shared | The canonical TypeScript types shared across the platform. |
Browser support
Modern evergreen browsers (Chromium, Firefox, Safari). Uses Shadow DOM, MutationObserver,
and the crypto / clipboard APIs.
License
MIT © Mohamed Ashraf Elsaed
