# `@foreloop/feedback`

The Foreloop feedback widget and typed client. People send feedback, bugs, and
ideas from inside your product — optionally annotated ("Show us where") and
screenshotted — straight into your Foreloop organization's loops.

## Install

Two paths. They install the same widget and accept the same options, differing
only in how the asset loads and how configuration is passed.

**Script tag** — no build step. The package ships the built browser asset at
`dist/foreloop-feedback.js`, so a plain site can skip npm entirely:

```html
<script
  src="https://cdn.jsdelivr.net/npm/@foreloop/feedback@latest/dist/foreloop-feedback.js"
  data-api-key="slpk_..."
  async
></script>
```

**npm** — when your app has a bundler and knows who is signed in:

```bash
npm install @foreloop/feedback
```

The API key must be a write-only `signal:write` key — it can submit feedback and
nothing else, which is why it may live in browser code. Generate one from your
Foreloop organization's Widget page.

Full step-by-step directions for both paths, including CSP requirements and a
troubleshooting table, are in
[Install and use the Foreloop feedback widget](../../docs/getting-started.md).

## JavaScript initializer

Use the initializer when your app knows who is signed in:

```ts
import { initForeloopFeedback } from "@foreloop/feedback";

await initForeloopFeedback({
  apiKey: "slpk_...",
  reporterName: signedInUser?.email,
  project: "subject_…", // optional Foreloop project target
});
```

`project` optionally scopes new submissions to a Foreloop project; omitting it
preserves default routing. `reporterName` pre-populates the widget's **Email**
field — the host resolves the signed-in user; the widget never infers identity
from browser state.

Headless (your own form UI):

```ts
import { sendForeloopFeedback } from "@foreloop/feedback";

await sendForeloopFeedback({
  apiKey: "slpk_...",
  comment: "The save action did not confirm completion.",
  severity: "high",
  url: window.location.href,
});
```

`sendForeloopFeedback` accepts an injected `fetchImpl` for servers, edge
runtimes, and tests.

## Options reference

Every option can be passed to `initForeloopFeedback({...})` or, for the plain
`<script>` embed, as a `data-*` attribute (camelCase → kebab-case:
`buttonColorDark` → `data-button-color-dark`). All are optional except the API
key.

| Option | Type / values | Default | What it does |
| --- | --- | --- | --- |
| `apiKey` (`data-api-key`, alias `foreloopKey`) | `slpk_...` string | — | Write-only `signal:write` key. Required. |
| `project` | `subject_...` string | — | Scopes new signals to a Foreloop project; omit for default routing. |
| `trigger` | CSS selector | — | **Changes the whole presentation.** Clicks on matching host elements open the widget (delegated document-level capture listener, so SPA re-renders keep working). The floating launcher disappears and the panel renders as a centered 420px modal over a dimmed backdrop (`aria-modal`, focus trapped, focus returned to the trigger on close). Without it, the widget is a floating bottom-right launcher with an anchored panel. |
| `buttonColor` | CSS color or `"transparent"` | icon defaults (`#171717`/`#fff`) | Themes the launcher chip in both tones. Contrasting ink is derived from the color's luminance automatically. `"transparent"` removes the chip so the glyph floats on the page, with ink following the page tone. |
| `buttonColorLight`, `buttonColorDark` | CSS color | `buttonColor` | Per-tone overrides. `buttonColorDark` shows over light pages, `buttonColorLight` over dark pages (named after the icon variants). |
| `buttonLabel` (`data-label` also works) | string | `"Foreloop Feedback"` | The launcher's `aria-label`. No visible text — the launcher is icon-only. |
| `placeholder` | string | none | Placeholder for the message textarea. By default the textarea is empty. |
| `productArea` | string | — | Recorded in the signal's Widget context. |
| `reporterName` | string | — | Pre-fills the Email field. The widget never infers identity itself. |
| `severity` | `"low"`..`"critical"` | unset | Pre-selects the Priority field (shown only for the bug category). |
| `sensitivity` | Foreloop sensitivity | `"customer_pii"` | Sensitivity stamped on the signal. |
| `includeQuery` | boolean | `false` | Include the page URL's query/fragment in the payload (stripped by default). |
| `includeUrlEvidence` | boolean | `true` | Attach the page URL as an evidence ref when no screenshot is attached. |
| `captureScreenshot` | boolean | `true` | Show the "Select capture area" section (browser consent flow; nothing captures silently). |
| `styleNonce` (`data-style-nonce`) | string | — | CSP nonce applied to the injected stylesheet for strict `style-src` hosts. |
| `scriptUrl` | URL | CDN default | Initializer-only: where to load the widget asset from. |
| `data-auto-init` | `"false"` | installs automatically | Script-tag-only: suppresses the automatic install on DOM ready so the host can call `window.ForeloopFeedback.init({…})` itself. |

### What the widget submits

Opening the widget shows two categories — **Automatic bug fix** and
**Automatic product improvement** (a support category exists but is not yet
listed). The chosen category configures the form (the bug category adds a
Priority field) and is recorded in the signal's `body_markdown` as
`- Category: <label>` under Widget context. Signals are `kind: "user_feedback"`
with sanitized page context, optional element annotations (selector + note),
and an optional screenshot uploaded as an attachment after the signal is
created. Filter downstream on the `Category:` line for routing.

### Behavior notes for integrators

- **Launcher tone**: the floating launcher samples the page background under
  itself (solid colors and gradient end-stops) and flips between the dark and
  light icon variants on scroll/resize/theme changes. `buttonColor` theming
  rides on top of this.
- **Dismissal**: clicking outside closes the panel only from the category menu;
  a form with typed content is never dismissed by a stray click (Escape,
  Cancel, and ✕ always work).
- **Re-initialization**: calling `init` again fully replaces the widget with
  the new options — safe to call on auth changes or SPA navigation.

## Supply chain

This package has no runtime dependencies — not a small tree, an empty one. A
clean install adds exactly one package:

```
$ npm install @foreloop/feedback
added 1 package, and audited 2 packages
found 0 vulnerabilities
```

If `npm audit` reports advisories after installing it, they come from something
else in that `node_modules` tree. `npm ls <package>` prints the dependency path
to whatever actually pulled the vulnerable package in.

Releases are published from GitHub Actions. They do not carry [npm
provenance](https://docs.npmjs.com/generating-provenance-statements)
attestations, because npm only accepts provenance from public source
repositories and this repository is private.

## Privacy defaults

- URL query strings and fragments are excluded unless `includeQuery` is enabled.
- Inputs, editable content, and elements marked `data-private`,
  `data-sensitive`, or `data-foreloop-private` can never be selected as
  annotations.
- Screenshots are optional and always go through the browser's own consent.
- Reporter identity is supplied by the host; the widget never infers it.
