No input values, ever.
The SDK sends field names, lengths and validity, never what was typed. Query strings are stripped and routes normalised. Do-Not-Track and Global Privacy Control are respected.
xlens maps every click to component → file:line, watches real sessions on your own server, and opens pull requests for the engineering, UX and UI problems it finds. Then it checks that the fix actually worked.
Most analytics tools record behaviour and work backwards to the code. xlens starts from the code, so every issue already knows its file, and so does the fix.
Clicks, navigation, form validity, API timing, errors and vitals. Field values never leave the browser, query strings are stripped, and routes are normalised.
import { init } from '@xlens/sdk';
init({
endpoint: 'https://ux.internal.example.com/v1/e',
app: 'shop',
});The build plugin stamps each element with a stable id tied to component → file:line, and the scanner maps every fetch to its call site. Works with Vite, webpack, Rollup, esbuild, Rspack and Next.js (Turbopack).
// <button data-xlens-id="1k3x9qa">Apply</button>
{
"uxId": "1k3x9qa",
"component": "Cart",
"file": "src/pages/Cart.tsx",
"line": 45,
"tag": "button",
"label": "Apply",
"screens": ["/cart"]
}Node and SQLite, no cloud. Issues are ranked by the share of a screen’s traffic they hit and what they cost in conversion. A newer build that is significantly worse gets flagged as a regression.
{
"event": "new",
"id": "UX-0005",
"kind": "dead-click",
"severity": "high",
"title": "\"Apply\" button on Cart does nothing when clicked",
"detail": { "affected": 99, "rate": 0.345, "screen": "/cart" }
}Each fix runs in a git worktree with repo-scoped tools. Edits to manifests, lockfiles, CI or .env are rejected. Only fixes that pass typecheck, build and test are committed, one commit per issue, and humans merge.
<label>
Postcode
- <input name="postcode" required pattern="[0-9]{6}"
+ <input name="postcode" required pattern="[0-9]{3} ?[0-9]{3}"
+ inputMode="numeric"
placeholder="e.g. 560 001" autoComplete="postal-code" />
</label>After a fix ships, the server waits for a later build with enough traffic on that screen and compares the issue’s rate before and after. The fix is marked verified, not-improved or pending.
"fix": {
"status": "pr-open",
"pr": "https://github.com/acme/shop/pull/128",
"outcome": "verified",
"impact": {
"build": "c19a0e2",
"before": 0.41, "after": 0.03,
"sessions": 812, "significant": true
}
}Every issue is ranked by reach and cost, keeps a stable id across runs, and is marked resolved when it stops appearing. Open a file to see how it's detected.
GET /api/stock 503 · 15.2% of calls · Cart.tsx:28// Failures and slowness, pinned to the call site that caused them.
GET /api/recommendations p75 1.84s · ProductPage.tsx:19// Failures and slowness, pinned to the call site that caused them.
TypeError: Cannot read 'qty' · after click on Remove// Failures and slowness, pinned to the call site that caused them.
Remove (cart line) → TypeError · Cart.tsx:31// Failures and slowness, pinned to the call site that caused them.
/img/hero-autumn.jpg 404 · Home.tsx:12// Failures and slowness, pinned to the call site that caused them.
/ LCP p75 3.4s// Failures and slowness, pinned to the call site that caused them.
/products · 27% of views blocked// Failures and slowness, pinned to the call site that caused them.
"In stock only" INP 480ms · Products.tsx:41// Failures and slowness, pinned to the call site that caused them.
/products CLS 0.21// Failures and slowness, pinned to the call site that caused them.
Apply (coupon) · 34% of cart views · Cart.tsx:45// Where people struggle, even when nothing throws.
Apply (coupon) · 3+ clicks in 1.2s// Where people struggle, even when nothing throws.
postcode rejected 3.1× per session · Checkout.tsx:35// Where people struggle, even when nothing throws.
/checkout · 41% start, never submit// Where people struggle, even when nothing throws.
/size-guide → back in < 4s · 62%// Where people struggle, even when nothing throws.
Proceed to checkout · seen by 38% on mobile vs 97% desktop// Read straight from your source, then weighted by real traffic.
<div onClick> zoom · ProductPage.tsx:52// Read straight from your source, then weighted by real traffic.
placeholder "560 001" ∉ pattern [0-9]{6}// Read straight from your source, then weighted by real traffic.
/cart → /checkout · −46%// Decisions for humans, backed by funnels built from what converting users actually do.
checkout conversion · mobile 9% vs desktop 31%// Decisions for humans, backed by funnels built from what converting users actually do.
/size-guide · 28% exits// Decisions for humans, backed by funnels built from what converting users actually do.
Wishlist button · 0.4% use// Decisions for humans, backed by funnels built from what converting users actually do.
xlens mcp is a stdio MCP server. Your agent gets the issue, the evidence, the timelines and the source with line numbers: the same brief xlens fix uses. It reports the result back to your server.
claude mcp add xlens -e XLENS_TOKEN=… -- npx xlens mcp{
"mcpServers": {
"xlens": { "command": "npx", "args": ["xlens", "mcp"], "env": { "XLENS_TOKEN": "…" } }
}
}[mcp_servers.xlens]
command = "npx"
args = ["xlens", "mcp"]
env = { XLENS_TOKEN = "…" }The server never needs your source code or an internet connection. The machine that fixes never needs your users' data, only the issues. Credentials stay where they already are.
@xlens/sdk · ~4 KBxlens servexlens fix · xlens mcp// app model: xlens scan --push after each release, from build plugin → server
The SDK sends field names, lengths and validity, never what was typed. Query strings are stripped and routes normalised. Do-Not-Track and Global Privacy Control are respected.
Up to 40 characters from buttons, links and form controls. Emails become <email> and runs of digits become <num> before anything is sent. Turn it off with captureText: false.
Labels and errors come from browsers, so the fix engine gets them as data. Its tools can't reach anything outside the repo's editable folders and your configured checks.
xlens only ever opens PRs, with one commit per issue, so a reviewer can drop any of them. Manifests, lockfiles, CI and .env files are off limits.
Playwright tests what you expect users to do. Session tools show what they did. xlens finds what they actually hit, traces it to the line, and fixes it. Use it alongside the tools you already have.
| Capability | xlens | Playwright | PostHog | FullStory / LogRocket | Sentry | Datadog RUM |
|---|---|---|---|---|---|---|
| Maps findings to component → file:lineOthers map JS errors through source maps, but not clicks, forms or UX issues. | ✓ | — | — | — | ◐ | ◐ |
| Real-user sessionsPlaywright drives scripted, synthetic browsers. | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| Dead and rage clicks | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| Root cause found in codeFor example, a validation pattern that rejects its own placeholder. | ✓ | — | — | — | ◐ | — |
| Funnels from real paths | ✓ | — | ✓ | ✓ | — | ◐ |
| Writes the fix and opens a PRAI fix features elsewhere focus on errors and exceptions. | ✓ | — | — | — | ◐ | ◐ |
| Verifies the fix on later buildsxlens runs a one-sided z-test on the issue rate in builds after the fix. | ✓ | — | — | — | ◐ | — |
✓ yes · ◐ partly · — no · Based on public docs as of Nov 2026. Spotted a mistake? Open an issue.
// TODO: xlens cloudA hosted server for teams that would rather not run one. Same detectors, same PRs.
Join the waitlistYes. xlens is MIT licensed and runs on your own infrastructure. The only cost is the model tokens for auto-fixes, billed by your own provider. Plug in a CLI agent instead and that cost is whatever you already pay.
Node 22.13+ for the server (it uses node:sqlite, so no native dependencies). React apps with Vite, webpack, Rollup, esbuild, Rspack or Next.js. More frameworks are on the roadmap.
Maybe later, for teams that would rather not run a server. Self-hosted xlens will stay free and complete.
Nowhere you don’t control. Sessions stay on your server, source stays on your machines, and fixes go through your own git and gh logins.
❯ npm i @xlens/sdk && npm i -D @xlens/plugin @xlens/cli ❯ npx xlens init Created xlens.config.json ❯