Embeddable, no-install, framework-agnostic end-user feedback — a drop-in widget
that lets any visitor of your deployed web app report a bug or request a feature,
with useful context gathered automatically, routed to a pluggable backend (GitHub
Issues first). Works standalone; integrates cleanly with
enlace as an optional add-on, but does not
depend on it.
Status: design phase. The competitive landscape, the design rationale, and the roadmap live in
misc/docs/. Work is tracked in GitHub issues; design decisions in GitHub discussions.
Developer-side tools (browser extensions, Chrome DevTools MCP, Jam, …) only help
you. They can never serve an ordinary end user reporting a problem on your live
site, or asking for a feature. heed is the missing piece: a public, zero-install
capture-and-report widget plus a small self-hostable backend that you own and that
routes wherever you want.
- Widget — one
<script>tag injects a Shadow-DOM-isolated custom element; drops into React, plain HTML, or server-rendered pages alike. Captures a screenshot, page URL, and environment by default; console/network capture is opt-in (privacy). - Backend — a mountable FastAPI router (run on its own, or mounted into
enlace) that validates, stores attachments (viadol), and dispatches to a pluggable sink. - Sinks (strategy pattern) — GitHub Issues / Discussions first; email, Slack, a built-in board, or a database later.
- Agent hand-off — an optional MCP server (
py2mcp) so Claude Code can pull the feedback queue and act on reports.
- Standalone first. No hard
enlacedependency; enlace integration is an optional[enlace]extra with lazy imports. - Privacy by default. Heavy capture (console, network, session replay) is opt-in; PII masking is designed in, not bolted on.
- Public endpoint = hostile input. Rate limiting, origin checks, payload caps, and bot protection are first-class — the ingest endpoint is unauthenticated by nature.
- Progressive disclosure. One script tag works out of the box; everything is parametrizable via keyword-only options with smart defaults.
pip install heed # core (backend + models)
pip install "heed[github]" # + GitHub sink
pip install "heed[enlace,mcp]" # + enlace mount + MCP feedback queue
pip install "heed[redis]" # + rate limits shared across workersThe POST /report endpoint is public, so it ships hardened: origin allow-list with CORS, streamed body/field/screenshot caps, strict input validation, per-IP and per-origin rate limits, a honeypot field, and an optional Turnstile/hCaptcha gate. Configure it in code or from HEED_* environment variables:
from heed import HeedConfig, StoreSink, make_app
config = HeedConfig.from_env() # e.g. HEED_ALLOWED_ORIGINS=https://myapp.example
app = make_app(StoreSink({}), config=config)For production, set allowed_origins, trusted_proxies (behind a reverse proxy), rate_limit_backend="redis" with redis_url (several workers), and bot_secret to turn the bot gate on. Embedders also need a Content-Security-Policy that allows the widget: see misc/docs/embedding-security.md.
The widget's category picker sets Report.category; the sink decides what each category becomes:
from heed import CategorySink, CategoryTreatment, GitHubIssuesSink, github_sink_from_token
issues = github_sink_from_token("myorg/myapp", token) # bug -> labelled issue with full context;
# feature/question -> light issue (no env dump)
ideas = github_sink_from_token("myorg/ideas", token, public_label="board")
sink = CategorySink({"feature": ideas}, default=issues) # or route a category to another sinkGitHubIssuesSink(treatments={Category.feature: CategoryTreatment(("idea",), title_prefix="[idea] ")}) changes labels, title prefix and detail ("full" | "light") per category. For a future public board, sinks that can list their items implement public_items(); the GitHub sink returns issues a maintainer labelled public as PublicItems (title, category, status from labels or issue state, link, 👍 votes, dates, never the issue body or the reporter; the item's link opens the issue itself, so keep the tracker public-safe), and for feature and question reports the server also drops the screenshot, console, network and browser details (HeedConfig.light_categories). heed.roadmap(items) / heed.changelog(items) are the two views over them.
The backend serves the widget too, so a site adds one tag:
<script async src="https://feedback.example/heed/widget/heed.js"></script>Options (data-* or heed.init({...})), the heed API, the React wrapper, screenshots and their masking, pinning a build with SRI, events, theming and CSP notes: widget/README.md.
misc/docs/research-report.md— the competitive landscape: what to reuse / steal / avoid, design patterns, and gotchas (deep research with adversarial verification).misc/docs/design.md— architecture, the data model, the sink interface, and the spectrum of approaches with pros/cons.misc/docs/embedding-security.md— hardening knobs for operators and CSP guidance for sites that embed the widget.
Author: Thor Whalen. License: Apache-2.0.