sluglist

Use case

A “Report a problem” button for beta & production

Once real users are in the product, “email us a screenshot” stops working. The production preset turns sluglist into a privacy-safe “Report a problem” button: form inputs masked in screenshots, PII scrubbed out of collected text, a dismiss ✕ for users who don't want it — and every report delivered to infrastructure you own.

ForReal users in your beta or production app.

01

The report arrives with the context you would have asked for

Route, viewport, browser, the console errors from the last minute and the actions before the click — instead of “it doesn't work on my end”.

02

Privacy is the default, not a setting you remember

One preset masks every form input in the screenshot, scrubs emails, long digit runs and tokens out of collected text, and asks consent before attaching an image.

03

It cannot become your problem

Everything the widget wraps calls the original, it uninstalls itself after repeated internal failures, and undelivered reports wait in an offline outbox.

One preset, safe defaults

const widget = createFeedbackWidget({
  project: "acme",
  preset: "production",        // masking + consent + scrub + dismiss
  connectors: [new HttpConnector("/api/feedback", () => session.token)],
  identity: { userId: user.id, email: user.email },  // → reporter in artifacts
  custom: { plan: user.plan, appVersion: APP_VERSION },
});
const ui = mountFeedbackWidget(widget);
footerLink.onclick = () => ui.show();   // rescue path after dismiss

preset: "production" turns on, all at once: input masking in screenshots, a screenshot-consent checkbox, PII text scrubbing, a dismiss ✕, and forced-off console.warn capture. Every option can still be overridden explicitly. See the preset table.

What PII protection actually means here

  • Screenshots: every input, textarea and select is redacted before the render; anything marked data-private is always redacted. The live DOM is untouched — masking happens only in the rendered image.
  • Text: emails → [email], long digit runs → [digits], hex/base64 tokens → [token] — across element text, URLs, error messages and the action trail. Dates, versions and stack-trace line numbers survive, so reports stay readable.
  • The trail records facts, not content: typing is logged as a character count; password fields aren't logged at all; navigation paths drop the query string.
  • Collected automatically: URL path, viewport, browser/OS, timezone, color scheme, recent errors. Deliberately not collected: full user agent, IP, cookies, storage, geolocation.

Your infrastructure, not ours

There is no sluglist server. Reports go to connectors you configure — the recommended shape is a ~50-line API route in front of your own storage, holding the credentials server-side. The widget makes no other network requests, and that claim is enforced by an automated test.

It cannot break your app

Everything the widget wraps (fetch, console.error, history) always calls the original; after repeated internal failures it uninstalls itself and gets out of the way. Undelivered reports persist in an IndexedDB outbox and retry on the next load.

Real users, real languages

Label bundles ship for English, Russian, Ukrainian, Spanish and German — one line to apply, with correct plural rules. Your own copy (categories, checklist titles, form labels) stays yours. See Localization.

Then what?

A user report is the same artifact a teammate or an agent produces, so it drops straight into the fix loop: pull the session folder into the repo and the agent reads it exactly as if you had clicked it yourself.

Before you ship

Walk the production checklist: env gating, a delivery token, server-side validation, retention, and a privacy-policy paragraph to adapt.

Frequently asked questions

Is it safe to store the screenshots?

The production preset masks every form input in the rendered screenshot, always redacts elements marked data-private, and adds a consent checkbox — the reporter chooses whether a screenshot is attached at all. The live page is never modified.

Do I need a backend?

A thin endpoint, yes — roughly 50 lines. It holds your storage credentials, validates a bearer token, rate-limits, and writes artifacts to your own storage. Write-keys never ship to the browser.

Can users turn the widget off?

Yes — the production preset adds a ✕ on the launcher that hides the widget and remembers the choice (7 days by default). ui.show() wired to a footer link is the rescue path back, and ui.open() starts the reporting flow from your own menu item without the launcher at all.

Is there an inbox or ticketing built in?

No, by design. sluglist is one-way capture with a stable artifact format; pipe it into the tracker or workflow you already run. No accounts, no statuses, no replies.

The other ways it is used