@signiflydev/feedback-widget
v0.5.0
Published
Signifly feedback widget — activation-link gated, native screenshot capture, reports into signi-ops
Readme
@signiflydev/feedback-widget
The canonical client-facing feedback capture channel (spec #443, decisions #430/#438/#439). Embedded in Signifly-built sites; files Feedback Reports into signi-ops against the site's Service.
How it works
- Invisible by default. The widget renders nothing unless the device has been activated. End users never see it.
- A launcher that keeps out of the way. Once revealed it is a 48px circle carrying the same
message-squareicon signi-ops uses for Feedback; hovering or focusing it widens that same button back out to the labelled pill, and opening the panel holds it open. There is no hover on a phone, so there it stays a circle — the accessible name is on the button either way. Where it sits is the install's choice: any of the four corners, or a free position in CSS lengths. See Placement below. - Activation links. signi-ops issues personal signed links (
https://clientsite.com?signifly-feedback=<token>). Opening one signs the reporter in passwordlessly (Supabase session minted server-side), sets a long-lived device cookie, and reveals the widget. Links are revocable per person from signi-ops → Settings → Feedback widget. - Real pixels. Desktop capture uses native
getDisplayMedia(share-this-tab,preferCurrentTab) — a single real frame of exactly what the reporter sees. DOM-rendering libraries are deliberately not used (#439). Attaching an image file is offered alongside it on every platform, not just mobile: since 0.4.0 a screenshot is required, and a dismissed share dialog, a host page withholdingdisplay-capture, or an insecure context would otherwise lock the reporter out of reporting at all. - Annotate + describe. Arrow, box, circle, freehand and text over the screenshot, with undo/redo and a text mark you can click to retype. On desktop the marks are made in a fullscreen overlay (the inline canvas stays arrow-only — at thumbnail scale a placed mark lands where nobody meant it); below 640px the panel becomes a sheet with a Screenshot tab and a Details tab. Plus kind (bug / feature request), title, and description.
- Title, description and screenshot are required as of 0.4.0 — enforced in the widget, not at the ingest. Client repos pin an exact version (#438), so
postBodySchemamust keep accepting everything every deployed widget can send; tightening it would 400 un-bumped sites with a failure that looks like an outage. See signi-ops ADR-0040. - Authenticated ingest. Reports submit through the reporter's Supabase session against the signi-ops feedback ingest (ADR-0006 / ADR-0007). The public site key does two jobs only: routes the report to its Service and gates the request origin. Without a session, a site-key-only fallback still accepts the report, flagged
unverified. - Browser metadata (user agent, viewport, locale, timezone, page URL/title, widget version) is captured automatically, including recent
console.warn/console.erroroutput and uncaught errors.
Install
Public npm (root ADR-049). No registry configuration, no token, nothing to arrange:
pnpm add @signiflydev/[email protected]Client repos pin an exact version — each release rolls out as a bump PR gated by that repo's CI and Vercel preview, and there is deliberately no CDN-latest (decision #438).
Inside this monorepo the dependency is workspace:* instead, so changes to the widget
are picked up without a release and no registry is involved at all.
import { initFeedbackWidget } from "@signiflydev/feedback-widget"
initFeedbackWidget({
siteKey: "sfk_…", // from signi-ops → Settings → Feedback widget
apiBase: "https://ops.signifly.com",
})Or as a script tag (IIFE build):
<script
src="/vendor/feedback-widget.iife.js"
data-site-key="sfk_…"
data-api-base="https://ops.signifly.com"
defer
></script>Pin an exact version in client repos. Releases roll out as automated version-bump PRs gated by each repo's CI and preview — never CDN-latest (#438).
Placement
placement says where the launcher sits. It defaults to "bottom-right", which is where
every install had it before the option existed, so leaving it out changes nothing.
initFeedbackWidget({
siteKey: "sfk_…",
apiBase: "https://ops.signifly.com",
placement: "top-left", // or bottom-left, top-right, bottom-right
})For a site whose own furniture is already in all four corners, pass CSS lengths instead —
plain numbers are pixels, and anything CSS accepts as a length (%, rem, vh, calc())
is passed through:
initFeedbackWidget({
siteKey: "sfk_…",
apiBase: "https://ops.signifly.com",
placement: { bottom: "50%", left: 24 },
})Only one side of each pair survives — top beats bottom, left beats right — because
the launcher has no width of its own and naming both would stretch it across the viewport.
A length in a unit the widget will not vouch for is refused rather than escaped, and the
launcher falls back to the default corner: it goes into a stylesheet inside the shadow
root, and a launcher nobody can find is worse than one in the wrong place.
The panel is anchored to the launcher wherever that ends up — it takes the launcher's nearer horizontal edge and opens away from the nearer vertical one, so a top placement drops down and a bottom one rises. Below 640px it is still a full-screen sheet regardless.
The script tag takes the same choice as data-placement, or as named sides for a free
position (data-top, data-right, data-bottom, data-left). A named side wins over a
corner:
<script
src="/vendor/feedback-widget.iife.js"
data-site-key="sfk_…"
data-api-base="https://ops.signifly.com"
data-placement="bottom-left"
defer
></script>signi-ops asks for the corner when it opens the install PR (Feedback → Roll out), stores
it on the Service, and writes it into the generated signifly-feedback.ts — so a re-run of
that PR does not quietly move the launcher back.
Registry
Published to the public npm registry as @signiflydev/feedback-widget (root ADR-049,
superseding ADR-048).
Releasing is merging. Bump version here and WIDGET_VERSION in
apps/signi-ops/lib/onboarding/widget-release.ts — a test asserts they match — and
.github/workflows/feedback-widget-release.yml publishes once that lands on main. It
skips versions npm already has, so a README fix does not try to republish. There is no
NPM_TOKEN: CI authenticates by OIDC trusted publishing, which also attaches a provenance
attestation naming the commit that built the tarball.
Publishing by hand needs an npm account with publish rights on the scope, and should be a recovery path rather than the route:
pnpm --filter @signiflydev/feedback-widget build
cd packages/feedback-widget && npm publish --otp=<code>The package contains no secrets. The site key is public by design, and files limits the
published tarball to dist — the same compiled JavaScript already bundled into the public
JS of every site that embeds the widget. The TypeScript source stays in this repository,
which is private.
Development
pnpm --filter @signiflydev/feedback-widget test # vitest (pure seams)
pnpm --filter @signiflydev/feedback-widget typecheck
pnpm --filter @signiflydev/feedback-widget build # esbuild ESM + IIFE, d.ts via tsc